一.分类
1. 未授权访问
2. 逻辑漏洞,主要存在交互的地方的功能函数上。
3. 潜在简单:SQL注入,SSRF,XSS,任意文件读取,文件包含,任意文件下载,文件删除,RCE,远程命令执行,反序列化
4. 程序功能漏洞,如使用foreachextract注册变量,$$name变量覆盖,parse_str只有一个参数引发变量覆盖,使用array_merge合并数组的,数据操作方面的(比如在使用正则表达式的时候捕获捕获组,简单替换,替换一次)
5. 对数据的处理不当,这个和上面的也是属于对数据的处理不当,但是有不同。
6. 对一些数据不当比较,很多函数也可以绕过
二.知识点
md5()
sha1()
ereg()
preg_match()
strcmp()
strpos()
strlen()
众所周知,php的底层是c(以标志字符串结束)。所以低版本的php的eregi函数在遇到%00后就会截止。so:eregi('abc%00','abcdefg')==1。高版本的php已弃用此函数
默认整形变量遇到字符123abc取123
in_array()只有两个参数时使用弱类型比较
变量覆盖
extract()
parse_str()
import_request_variables()
mb_parse_str()
$$
register_globals通过REQUEST自动注册变量
RCE
eval()
assert()
preg_replace() /e *e模式
create_function()
array_map()
array_filter()
array_walk()
call_user_func()
call_user_func_array()
usort()
uasort()
$a($b)动态函数
system()
passthru()
exec()
shell_exec()
popen()
proc_open()
反引号
ob_start
文件相关
config/./database.php == config/database.php
include();
include_once();
require();
require_once();
cuit_init() #curl可以使用file伪协议读取文件。
highlight_file();
show_source() #将指定文件的源代码输出
file_get_contents();
fgets() #从文件中读取一行数据。
fgetss()
parse_ini_file()
fgetcsv() #从CSV格式的文件中读取一行数据。
fread(文件指针,length)
file() #将整个文件读入一个数组中,每个数组元素对应文件中的一行。
readfile() #将文件直接输出到浏览器,而不需要先读取到字符串中。
simplexml_load_string #读取xml文件
fwrite(文件指针,内容) #读文件内容 参数可为文件名,也可为网络资源
file_put_contents(文件名,内容)
fputcsv(文件句柄,内容) #将一行数据以CSV格式写入文件。
copy(); #复制文件,可以复制网络资源
move_uploaded_file #移动文件
set_ini()
unlink("dd/../../1.txt"); #dd必须存在
rmdir()
弱类型
"123abcd" == "123";
有弱类型比较就一定有漏洞
if(0=="*"){
echo "flag";
}else{
echo "fail";
}
in_array传入数组绕过
array_search默认采用弱类型比较
strcmp传入数组发生错误返回0(相等)
switch默认弱类型比较
isset和empty遇到 空格、0、0.0、"0"、NULL、数组、未赋值的变量
SSRF
file_get_contents()
fsockopen()
curl_exec()
get_headers()
fopen()
readfile()
三.入手点
框架核心基类
审计框架先从加载基础类看起,在new基础类的那里打上断点,依次看new的是什么类?然后就可以跟进去看个大概,就知道整个框架的运行流程了。
路由
然后是路由,路由就像是人体的骨架和脉络,不知道路由就算代码看的再明白也没有用。
大概看懂控制器的作用
然后是各类控制器,先看懂各个模块各个控制器的作用。
变量的注册
然后看变量如何传入的,这里不是指的简单的$_REQUEST变量赋值,而是封装过了的变量赋值方法,可能就会存在变量覆盖。
WAF
看WAF,addslashes,htmlspecialchars。有PDO基本上就不存在SQL注入了,不过可以快速搜索下PDO是否使用了query方法,找堆叠注入。
繁杂的数据处理
变量覆盖,字符串解码,对字符串进行的各种解开的操作,str_replace,urldecode,jsondecode之类的。RCE,如果前面的都找不到突破点RCE基本没戏。文件上传,文件下载,文件删除,SSRF,反序列化,XSS。
收益越小的,难度越大的放到后面审计。
外紧内松
成熟的开发者不使用开源框架的话一般就是使用自己公司或者自己的框架,框架的安全特点就是外严内松,SQL注入RCE XSS 文件上传 文件下载之类的被过滤拦截的死死的,但是一旦通过变量覆盖,字符串解串绕过了,那整个安全框架的防线都会被打开,由突破点可以扩散出各种漏洞。
四.安全隐患存在的地方
任意文件上传,读取
下载文件的地方(前台or后台),一般叫什么download,get,file,getconfig之类的
上传文件的地方(前台or后台),绕过上传文件
逻辑
添加用户的地方,发现逻辑漏洞
修改信息和查看敏感信息的地方不允许普通用户操作,则审计这类地方。可能有逻辑漏洞越权
双重置0之验证码绕过
验证码通过$_POST['captcha']传递,如果与通过$__session['cptcha']传递的校验码相同,就可进行暴力破解,这两者都在http请求包中可控,把这两者都置为空。
未授权方法
如果方法没有if (!session('?user'))这样的鉴权,并且没有挂钩
未授权方法本质上还是逻辑错误:例如:$access['is_auth']是0,但是empty($access['is_auth'])直觉上是返回0,但是实际上是返回1,因为$access['us_auth']是切切实实存在的,不管它的值是多少
未授权方法根据数据库中存储的SystemNode确定此路径是不是system专属。大概实现
//工具node查询是不是System的Node,如果是,返回的结果里带着is_menu
$info = Db::name('SystemNode')->where('node', $node)->find();
#HOOK方法起钩子
Hook::listen('action_begin', $call); $call为方法
/application/tags.php
'action_begin' => ['hook\AccessAuth'],这样的钩子里认证。
数据库相关
使用array_merge把数据放入数组的,然后把该数组通过db或者m模型存入数据库的,就会造成逻辑漏洞了。(严重的逻辑漏洞)
递归查询注入除了union select 语句可以双重查询外,还可将union查询出的结果做为参数传入另一条语句,例如update,insert into。
insert into comment(user_name,comment_text,pub_date)value('xxx',',(select admin_pass from admin limit 0,1),1);#',now());
update多同条件
updata user set name=1 name=2 where id=1;
文件上传相关
如果限制了文件后缀名,可注释掉后缀名(在文件名存入数据库的时候)。where id=984562 id=../../phpinfo.php--+.png。
文件包含相关
zip绕过
phar伪协议绕过
include($_GET['path']."inc"); //?path=phar://uploads/u_时间戳_v.png/v.inc
SQL注入相关
#sql注入控制越前面的内容越可以挖掘到注入。thinkphp在使用模型选择表的时候可以控制表名后的内容来达成注入M()->table(I('table'))->where("id=1")->find()。等于select * from '$_GET[table]' where id = 1 limit 1
TP3可能存在SQL注入的方法:
传入数组注入
select()
find()
delete()
表达式注入
主要是获取参数的选项没有使用推荐的I方法,I方法可在表达式后加上空格,使其不解析表达式
绑定注入
save()->update() 绑定
setField()
setInc()
setDec()
ParseOption注入
M('user')->select($test);
主要就是把where的选项放在了最后面,而不是单独使用where()方法
update
where()
table()
field()
alias()
union()
order()
group()
having()
comment()
force() //force index
query()
execute()
count()
where(array) //当array里有_string,那么这就是一个组合查询
where(array('_string'=>'name=tom'))->select() //where xx & name=tom
#因为是where参数是数组类型的,在where内部会做一次类型判断,并且强转id参数为数字型。
$user = M("user");
$map['id'] = I('id');
$user->where($map)->select();
#不安全的字符串条件查询
M('user')->where('id='=.I('id'))->find();
模板相关
如果TMPL_ENGINE_TYPE模板引擎是原生的php模板,那就存在模板注入
五.代码审计快速匹配规则
#变量覆盖
$$[a-zA-Z_x7f-xff][a-zA-Z0-9_x7f-xff]*
extract()
parse_str()
import_request_variables()
mb_parse_str()
shi'yon$$
匹配$$a这样的
#解码
strip_tags()
stripslashes()
htmlspecialchars_decode()
html_entity_decode()
get_html_translation_table()
get_html_translation_table()
urldecode()
rawurldecode()
json_decode()
filter_var()
preg_replace()
preg_match()只匹配了结尾没匹配开头,或者只匹配开头没匹配结尾
str_replace()
base64_decode()
parse_url()
mt_rand()
mb_strtolower()
exploade()
intval()
ereg()
eregi()
strpos()
strlen()
#弱类型
==
strcmp()传入数组
switch
isset()
empty
in_array()
#RCE
eval()
assert()
preg_replace() /e *e模式
create_function()
array_map()
array_filter()
array_walk()
call_user_func()
call_user_func_array()
usort()
uasort()
$a($b)动态函数
system()
passthru()
exec()
shell_exec()
popen()
proc_open()
反引号
ob_start
#文件相关
include();
include_once();
require();
require_once();
cuit_init() #curl可以使用file伪协议读取文件。
highlight_file();
show_source() #将指定文件的源代码输出
file_get_contents();
fgets() #从文件中读取一行数据。
fgetss()
parse_ini_file()
fgetcsv() #从CSV格式的文件中读取一行数据。
fread(文件指针,length)
file() #将整个文件读入一个数组中,每个数组元素对应文件中的一行。
readfile() #将文件直接输出到浏览器,而不需要先读取到字符串中。
simplexml_load_string #读取xml文件
fwrite(文件指针,内容) #读文件内容 参数可为文件名,也可为网络资源
file_put_contents(文件名,内容)
fputcsv(文件句柄,内容) #将一行数据以CSV格式写入文件。
copy(); #复制文件,可以复制网络资源
move_uploaded_file #移动文件
set_ini()
unlink("dd/../../1.txt"); #dd必须存在
rmdir()
#SSRF
file_get_contents()
fsockopen()
curl_exec()
get_headers()
fopen()
readfile()
#反序列化函数
(unserialize($.*))|(unserialize_callback_func($.*))|(unserialize_object_func($.*))
#析构函数中出现了函数调用的
publics+functions+__destructs*([^)]*)s*{(?:[^{}]*{[^{}]*})*[^{}]*bw+s*((?:[^()]*|${[^}]+}){1,5})s*;s*}
#查找某个方法
functions+saveDeferreds*(
#查找x类中的x方法
classs+类名s*{[sS]*?functions+方法名(
#phar://伪协议触发
(^(.{1})|s|n|r|t|@)(?:fileattime|fileectime|file_exists|file_get_contents|file_put_contents|file|filegroup|fopen|fileinode|filemtime|fileperms|is_dir|is_executable|is_file|is_link|is_readable|is_writable|is_writeable|parse_ini_file|copy|unlink|stat|readfile|exif_thumbnail|exif_imagetype|imageloadfont|imagecreatefrom|hash_hmac_file|hash_file|hash_update_file|md5_file|sha1_file|get_meta_tags|get_headers|mime_content_type|getimagesizefromstring|getimagesize|finfo_file|finfo_buffer|extractTo|pgsqlCopyFromFile|)bs*(s*(?:(?:'[^']*')|(?:"[^"]*")|(?:[^'"()]*))*s*)
六.安全防范
不安全的注册
在这里,很明显的出现了变量覆盖,采用了foreach和$$这样的结构注册变量
foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v)
{
${$_k} = $_v;
}
}
安全的注册
规定了变量的名字为nvarname,所以只能注册nvarname这一个变量
foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v)
{
if($_k == 'nvarname'){
${$_k} = $_v;
}
}
}
参考链接:
https://www.kancloud.cn/code7/tp5/232970
希望能与各位志同道合的审计兄弟一路同行,共同进步。如果想与作者交流,可以后台回复:php审计
原文始发于微信公众号(安全初心):php代码审计方法论
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论