0x4qE's Blog 


HGame 2020 week2 WriteUp


前言

到了week2,菜鸡开始不会做了…只akWebMisc,最后一天做出两道Crypto,努力与大佬们减小差距~

Web

Cosmos的博客后台

进入题目注意到地址自动跳转到/?action=login.php,于是想到用php伪协议读取源码(?有了提示我才想到)。访问/?action=php://filter/read=convert.base64-encode/resource=login.php

拿到了login.php的源码

base64解码以后

看一下登录需要的条件

1
$admin_password == md5($_POST['password']) && $_POST['username'] === $admin_username

输入的密码md5()加密以后和数据库里的密码要一致,可我不知道数据库里的密码呀?想用php伪协议读取config.php却看到一行Hacker fet out!那么就不能用这种方法读取密码了。在这里注意到有一个DEBUG模式,并且第12行有一个eval(var_dump($$debug)),这里有两个$,百度搜一下以后,发现可以在这里传入/?action=login.php&debug=GLOBALS,读取php全局变量。

admin_password0e114902927253523756713132279690,要让passwordmd5值与它相等,这里考的就是php弱类型比较,当md5值为0e开头时,就会被当成科学计数法,当成数字的比较,于是两边0==0,结果就是true。百度找一个值s155964671a

于是username值为Cosmos!password值为s155964671aPOST登陆上去,可以看到这里成功跳转了。继续读取admin.php源码。

这里又有parse_url(),又有curl()。首先我们POST传入img_urlparse_url()通过检查url限制我们只能访问localhost或者timgsa.baidu.com,然后通过curl()访问我们传入的img_url,下载图片。这里可以通过利用parse_url()curl()两个函数不同的解析方式,访问根目录下的flag目录。

具体可以参考这篇分析

首先试着读取/etc/passwd,我们传入file://localhost/etc/passwd,发现能够执行,那边可以直接读flag了,于是传入file://localhost/flag

base64解码以后便可以得到flag

hgame{pHp_1s_Th3_B3sT_L4nGu4gE!@!}

Cosmos的留言板-1

首先访问题目地址

尝试一下不同的id

/?id=4再往后就没有信息了。可以猜测这是一个SQL注入,于是我们测试几个语句,看看它过滤了什么。

万能密码是可行的,同时又有额外发现,后台把传入的所有空格都过滤了。除此以外试了许多,发现select被过滤了,但是可以通过大小写seLect来绕过。

除此以外没什么发现了,于是在此卡住……实在想不出来,于是去问🍆,他让我去搜索SQl注入+过滤空格有关的内容,一下子就找到了。看来是因为我搜索关键词的能力还不够找到网上的这篇文章,依葫芦画瓢,flag就出来了。很轻松。

通过这道题,学到了一些绕过后台过滤的方法,还有爆库,爆表,爆字段的一些方法。于是在这里总结一下。

  • 通过SQL语句的块注释符/**/绕过空格的过滤

  • 通过union联合搜索可以爆出数据库中的一些数据

  • concat()将多个字符串拼接成一个字符串

  • information_schema这个数据库中保存了MySQL服务器中所有数据库的信息

    • tablescolumnsschemata都是这个库中的表
    • table_name在表tables中,记录了所有数据库的的名字。
    • table_schema在表tables中,记录了所有数据库的名字。
    • column_name在表columns中,记录了所有数据库的的名字。
  • database()函数返回值是当前数据库的名字。

于是开始注入

爆表名

得到一个叫f1aggggggggggggg的表

爆字段名

得到一个叫fl4444444g的字段

爆出flag

hgame{w0w_sql_InjeCti0n_Is_S0_IntereSting!!}

Cosmos的新语言

刚开始就卡了很久,看到file_get_contents想到会有源码泄露,但是用php伪协议一点反应都没有,后来恍然大悟,原来只要访问/mycode就可以看到源码了,是我想多了2333。

🍆说这道题就是要考验我们写脚本的能力,先分析一下题目。解这道题的关键是,每5秒解密函数的顺序都会刷新(这还是🍆提醒我的)。所以只有用脚本分析出函数的顺序,然后解密之后再POST传过去,光靠手动是来不及的。接下来就开始写脚本了。

首先要获取密文

1
2
3
4
5
6
7
import requests
url = "http://af5eb091da.php.hgame.n3ko.co/"
r = requests.get(url)
# 不会高级方法,只能用截取字符串来获取密文
firstIndex = r.text.find('</code><br>')
lastIndex = r.text.find('<br></body>')
str1 = r.text[firstIndex+12:lastIndex-20]

接下来是分析加密函数,一个一个写出对应的解密函数。

decrypt()

decrypt()encrypt()的解密函数,encrypt()将字符串中的每一个字符的ascii码+1decrypt()就是把字符串中的每一个字符的ascii码-1

1
2
3
4
5
def decrypt(result):
origin = ''
for i in result:
origin += chr(ord(i)-1)
return origin

rot13()

phpstr_rot13()将字符串中的每一个字符都向前移动13个字母,因为英文字母总共就26个,所以它的解密函数就是它自身。我从网上找了一个str_rot13()python实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def rot13(s):
result = ""
for v in s:
c = ord(v)
if c >= ord('a') and c <= ord('z'):
if c > ord('m'):
c -= 13
else:
c += 13
elif c >= ord('A') and c <= ord('Z'):
if c > ord('M'):
c -= 13
else:
c += 13
result += chr(c)
return result

另外两个函数

python也有base64库,就不需要自己造轮子了,还有一个phpstrrev()函数,可以将字符串调换字母顺序,尾到头,头到尾。而python中只要用字符串切片就很好实现了。解密函数为str[::-1]

开始解密

获取加密顺序的思路就是通过字符串截取,用字符串比较判断用了什么函数。脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
p = requests.get(url+'/mycode')
text = p.text
firstIndex = text.find('echo(')
lastIndex = text.rfind("($_SERVER['token']")
text = text[firstIndex:lastIndex]
firstIndex = text.find('echo(')
for i in range(10):
methodIndex = text.rfind('(')
print(text[methodIndex:])
if ('base64_encode' in text[methodIndex:]):
str1 = base64.b64decode(str1)
str1 = bytes.decode(str1)
elif ('strrev' in text[methodIndex:]):
str1 = str1[::-1]
elif ('str_rot13' in text[methodIndex:]):
str1 = rot13(str1)
elif ('encrypt' in text[methodIndex:]):
str1 = decrypt(str1)
text = text[firstIndex:methodIndex]
# token就是解密以后的字符串了
token = str1

然后带上token发包就可以得到flag

1
2
3
4
5
6
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
}
q = requests.post(url, headers=headers, data={'token': token})
print(q.text)

小意外

因为我的base64解密总会遇到Incorrect Padding的问题,Google了好久也搜不到满意的结果,我太菜了,所以只能不断循环,直到加密函数里面没有base64再进行解密,相当于是在解密时加了一层判断,当返回头里没有base64_encode时,再进行解密。所以最终脚本如下,删除了一些调试语句:

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

# 用python实现str_rot13()

def rot13(s):
result = ""
for v in s:
c = ord(v)
if c >= ord('a') and c <= ord('z'):
if c > ord('m'):
c -= 13
else:
c += 13
elif c >= ord('A') and c <= ord('Z'):
if c > ord('M'):
c -= 13
else:
c += 13
result += chr(c)
return result

def decrypt(result):
origin = ''
for i in result:
origin += chr(ord(i)-1)
return origin

while 1:
url = "http://af5eb091da.php.hgame.n3ko.co/"
headers = {'Host': 'af5eb091da.php.hgame.n3ko.co/'}
r = requests.get(url)
firstIndex = r.text.find('</code><br>')
lastIndex = r.text.find('<br></body>')
str1 = r.text[firstIndex+12:lastIndex-20]

p = requests.get(url+'/mycode')
text = p.text
firstIndex = text.find('echo(')
lastIndex = text.rfind("($_SERVER['token']")
text = text[firstIndex:lastIndex]
firstIndex = text.find('echo(')
if('base64_encode' not in text):
for i in range(10):
methodIndex = text.rfind('(')
if ('base64_encode' in text[methodIndex:]):
str1 = base64.b64decode(str1)
str1 = bytes.decode(str1)
elif ('strrev' in text[methodIndex:]):
str1 = str1[::-1]
elif ('str_rot13' in text[methodIndex:]):
str1 = rot13(str1)
elif ('encrypt' in text[methodIndex:]):
str1 = decrypt(str1)
text = text[firstIndex:methodIndex]
token = str1
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
}
q = requests.post(url, headers=headers, data={'token': token})
print(q.text)
exit()

一般来说多跑几次就可以出flag了,不知为什么复现的时候跑了半个小时一直都没有成功,偶然的一次之后就又恢复正常了。

hgame{s!MpLE-5Cript~wItH_PyTH0N_OR_phP}

Cosmos的聊天室

尝试点击右上角的Flag is here.

这里提到了token,又是聊天室那么可以确定是XSS了,首先尝试输入<img

刚开始的时候看到信息是空的,我还以为<和以后的字符串全部被过滤了,因此尝试了好久,其实,如果打开F12看一看就完全不会卡住了,可以看到,虽然我只输入了一个未闭合的尖括号,浏览器自动帮我闭合,这就是利用了浏览器的容错性,而这点,还是🍆提醒我的。

我们再做一些观察,尝试输入,闭合的括号<img>scriptiframe等等,发现,闭合的尖括号会直接被过滤,而script,和iframe会直接被替换为HI THERE!,因此双写script这种方法就没有用武之地了。

在被提醒以后,我搜索xss+容错性,就找到了我想要看到的文章。找到与现在比较相似的情况,把payload拿来用,首先尝试弹窗。

1
<img onerror=&#97;&#108;&#101;&#114;&#116;&#40;1&#41; <img src=x

利用imgonerror和一个错误的src组合,当浏览器读取不了src里的图片时,就会执行onerror里面的语句。看,成功弹窗了!

接着就可以利用xss平台盗取管理员的cookie了,在这里我搭平台至少用了一天时间,首先是找在线的xss平台,比如xss.pt,可是等了半小时迟迟收不到cookie,气死我了,换了两个平台都不行,只好亲自动手,丰衣足食,最后找到了docker里的beef,成功搭起了我的xss平台

然后就可以构造xss语句,思路就是当图片加载失败的时候,让浏览器访问xss平台上的钓鱼脚本hook.js,然后xss平台立刻就会相应,返回管理员的相关信息,其中就包含了我想要的cookie。于是开始构造!

只要把onerror=后面的内容用Unicode后再放入Payload,即可成功执行。然后就等待一会即可收到cookie

于是访问/flag,用Chrome的插件Set this cookie修改浏览器的cookie,即可拿到flag

Crypto

Verification_code

源代码太长了,截取关键部分放上来。

这题的意思就是让我们求解验证码proof的前4位,简单是确实简单。在网上找了一个快速爆破的脚本,有兴趣的可以看看。其中用到了笛卡尔积,我还没来得及看证明,先解题了。直接上脚本。

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
from pwn import *
from hashlib import sha256
import string
import itertools
p = remote('47.98.192.231', 25678)
r = p.recvuntil('\n')
# 通过截取字符串获取proof和_hexdigest
r = bytes.decode(r)
_hexindex = r.find('== ')
_proofindex = r.find('XXXX+')
proof = r[_proofindex+5:_hexindex-2]
_hexdigest = r[_hexindex+3:].strip('\n')
# 爆破验证码
code = ''
strlist = itertools.product(string.ascii_letters + string.digits, repeat=4)

for i in strlist:
code = i[0] + i[1] + i[2] + i[3]
encinfo = sha256(code.encode() + proof.encode()).hexdigest()
if encinfo == _hexdigest:
print (code)
break
# 按要求发送
p.recvuntil('Give me XXXX: ')
p.send(code)
p.recvuntil('The secret code?')
p.send('I like playing Hgame')
p.interactive()

这个flag拿的很轻松,压轴上分最为致命。

Remainder

看看题目Remainder,看看描述烤个孙子,百度一下可以猜到这是一道关于中国剩余定理(又称孙子定理)的题。看看题目。

一长串数字看起来很吓人,分析一下其实就是一个线性同余方程组。我找到一个不错的分析过程,顺带脚本也有了,改改就能拿来用,具体请看这篇文章。解完以后,就得到一个含有多因子的模数M的模方程C = m**e mod M。于是又在网上找到了脚本,两个脚本结合一下,就跑出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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/usr/bin/env python2
# coding:utf8

from Crypto.Util.number import *
import gmpy2

n = 3
a = [78430786011650521224561924814843614294806974988599591058915520397518526296422791089692107488534157589856611229978068659970976374971658909987299759719533519358232180721480719635602515525942678988896727128884803638257227848176298172896155463813264206982505797613067215182849559356336015634543181806296355552543,
49576356423474222188205187306884167620746479677590121213791093908977295803476203510001060180959190917276817541142411523867555147201992480220531431019627681572335103200586388519695931348304970651875582413052411224818844160945410884130575771617919149619341762325633301313732947264125576866033934018462843559419,
48131077962649497833189292637861442767562147447040134411078884485513840553188185954383330236190253388937785530658279768620213062244053151614962893628946343595642513870766877810534480536737200302699539396810545420021054225204683428522820350356470883574463849146422150244304147618195613796399010492125383322922, ] # 余数
m = [94598296305713376652540411631949434301396235111673372738276754654188267010805522542068004453137678598891335408170277601381944584279339362056579262308427544671688614923839794522671378559276784734758727213070403838632286280473450086762286706863922968723202830398266220533885129175502142533600559292388005914561,
150088216417404963893679242888992998793257903343994792697939121738029477790454833496600101388493792476973514786401036309378542808470513073408894727406158296404360452232777491992630316999043165374635001806841520490997788796152678742544032835808854339130676283497122770901196468323977265095016407164510827505883,
145897736096689096151704740327665176308625097484116713780050311198775607465862066406830851710261868913835866335107146242979359964945125214420821146670919741118254402096944139483988745450480989706524191669371208210272907563936516990473246615375022630708213486725809819360033470468293100926616729742277729705727, ] # 模数

p = m[0]
q = m[1]
r = m[2]
factors = [p, q, r]
phi = 1
"""扩展欧几里得"""
def exgcd(a, b):
if 0 == b:
return 1, 0, a
x, y, q = exgcd(b, a % b)
x, y = y, (x - a // b * y)
return x, y, q

"""扩展中国剩余定理"""
def CRT():
if n == 1:
if m[0] > a[0]:
return a[0]
else:
return -1

for i in range(n):
if m[i] <= a[i]:
return -1

x, y, d = exgcd(m[0], m[i])
if (a[i] - a[0]) % d != 0:
return -1

t = m[i] // d
x = (a[i] - a[0]) // d * x % t
a[0] = x * m[0] + a[0]
m[0] = m[0] * m[i] // d
a[0] = (a[0] % m[0] + m[0]) % m[0]

return a[0], x, m[0]

e = 65537
ans, x, M = CRT()

for x in factors:
phi *= (x-1)
d = gmpy2.invert(e, phi)
print long_to_bytes(pow(ans, d, M))

Misc

Cosmos的午餐

压缩包里有一个.pcapng和一个ssl.log,百度了一下知道了如何导入.log日志文件,由此分析https的传输过程。我们通过编辑-->首选项-->Protocols-->TLS导入.log文件。找啊找,追踪TLS流,发现了传输过程中有一个.zip文件。

找到比较大的对话,将它以原始数据导出为.zip文件,把请求头等与.zip无关的内容删去,再保存,这样就可以得到一个压缩包了。将它解压,得到一个文件名Outguess with key。百度一下outguess是一个工具,那么key在哪呢?

注意到题干里有一个PS

于是我们打开图片属性

得到一个hidden.jpg

访问之后下载来一个压缩包,解压后打开,是一个二维码,扫一下就可以得到flag了。

所见即为假

下载,解压,打开后是一张图片

首先试试binwalkStegslove看看有没有LSB,都不行,在即将放弃的试着用记事本打开压缩包,终于找到了突破口!

于是搜索F5+隐写,找到了工具F5-steganography

得到一个output.txt,打开看看

观察发现可能是16进制文件,于是在010 Editor里输入这一串16进制数字。

成功!

地球上最后的夜晚

解压出来一个.pdf和一个加密的.7z压缩包,想到可能有pdf隐写,于是下载工具wbStego4open。开始Decode!解密得到解压密码。

解压后得到一个.docx文件,搜索.docx+ctf发现.docx的本质是压缩文件,所以改.docx后缀名为.zip,然后再解压。解压之后一个文件夹一个文件的翻,终于翻到了flag

玩玩条码

这道题卡了好久好久,多亏了ObjectNotFound学长的耐心提示,最终得以解出。

可以看到都需要解码,我首先看到的是7zipPasswordHere,于是想办法看看.mp4里藏着什么信息。经过学长的提示,下载了MSU Stego Video这个过滤器,在提取信息的时候发现需要输入密码。

那可能就需要去解那个JPNPostCode了,Google一下猜测这应该是一个日本邮政条形码。在网上找解码器是行不通的,我换了好几个在线网站都不能解出来,最终找到了在线条码生成器,手动去凑条码的图形,终于拿到了密码。

用这个密码去解.mp4,将视频文件另存为.avi,然后就可以看到password.txt里有.zip的密码了。

然后解压.7z压缩包,得到一个Code128,找一个工具扫一下就拿到flag了。

终于akMisc,好开心!






© - 0x4qE - 2019 - 2020 - Powered by hexo Themed by quark

浙ICP备19039917号-1
浙公网安备 33011802001799号

小破站跌跌撞撞