- 未授权访问
- 逻辑漏洞,主要存在交互的地方的功能函数上。
- 潜在简单:SQL注入,SSRF,XSS,任意文件读取,文件包含,任意文件下载,文件删除,RCE,远程命令执行,反序列化
- 程序功能漏洞,如使用foreach\extract注册变量,$$name变量覆盖,parse_str只有一个参数引发变量覆盖,使用array_merge合并数组的,数据操作方面的(比如在使用正则表达式的时候捕获捕获组,简单替换,替换一次)
- 对数据的处理不当,这个和上面的也是属于对数据的处理不当,但是有不同。
- 对一些数据不当比较,很多函数也可以绕过
审计框架先从加载基础类看起,在new基础类的那里打上断点,依次看new的是什么类?然后就可以跟进去看个大概,就知道整个框架的运行流程了。
然后是路由,不知道路由就算代码看的再明白也没有用。
然后是各类控制器,先看懂各个模块各个控制器的作用。
然后看变量如何传入的,这里不是指的简单的$_REQUEST变量赋值,而是封装过了的变量赋值方法,可能就会存在变量覆盖。
看WAF,addslashes,htmlspecialchars。有PDO基本上就不存在SQL注入了,不过可以快速搜索下PDO是否使用了query方法,找堆叠注入。
变量覆盖,字符串解码,对字符串进行的各种解开的操作,str_replace,urldecode,jsondecode之类的。RCE,如果前面的都找不到突破点RCE基本没戏。文件上传,文件下载,文件删除,SSRF,反序列化,XSS。 收益越小的,难度越大的放到后面审计。
成熟的开发者不使用开源框架的话一般就是使用自己公司或者自己的框架,框架的安全特点就是外严内松,SQL注入RCE XSS 文件上传 文件下载之类的被过滤拦截的死死的,但是一旦通过变量覆盖,字符串解串绕过了,那整个安全框架的防线都会被打开,由突破点可以扩散出各种漏洞。
这本质也就是渗透测试的思维,不是代码审计的思维。在面向流程的的程序中这种审计方法是足以应付的并且很高效,但是到了MVC中就不太起作用了
拿工具扫一遍扫到危险函数就去跟,这样不仅容易跟丢,而且可能跟了十多分钟参数就不可控了或者被过滤掉了。
常规的审计方法分为两种,它们都有各自的好坏
会开发才会审计
- MVC,控制器只是起到粘合作用,把外部传入的参数拿进来(在内部类中做过过滤拦截处理后的参数),然后开始调用各种模型来鉴权,最后调用目标模型来完成操作,最终渲染结果
从模型内推的优点很明显,能快速的发现危险函数及危险操作。在发现后心理会很兴奋,觉着漏洞就在这了,于是就开始兴奋的招调用链。但是它的缺点更明显,就是参数都是从控制器传入的,在模型内部很少可控参数。而且找调用链找着找着就容易跟丢。
从控制器外调的优点也很明显,能清晰的掌握调用链,并且参数在大多数情况清晰可见。但是很可惜,危险函数大多都存在于模型中。这里这是起一个粘合的作用。
这里的佛系审计不是指跟玩似的进行审计。意思是降低期待,带着理解程序整体的MVC整体流程来进行审计,这样就算没有收获,也能提升阅读代码的能力。
在这种审计方式里面最重要的思想就是抓大放小了,在动态调试工具的辅助下,一些不影响流程的片段可以直接看调试结果。
- 扩展插件
- 配置,助手函数,惯例配置
- 模型 在这看一遍就可,记住危险操作,大概记住模型逻辑,记住参数及返回值。 用notepad记下,要不然不便后续搜索
- 控制器 最后才是控制器,因为之前已经记住了模型,所以从这往内跟,会比较轻松一些。
重点:对内部的危险函数不必深究,因为工作量非常大,拿notepad记下即可,后续控制器用到的时候再搜索
框架的的外部内部安全机制十分严格,几乎无法突破,但是只要找到了突破点,后续的getshell就可以一路顺风
变量覆盖突破
array_merge 数据库查询造成任意字段写入
sql注入联合查询取出数据以此为依据处理执行操作
包含而非解析 配置
包含而非解析 模板
变量的注册本身没有问题,但是某个地方没有addslsches或者可以绕过则有问题
变量覆盖函数
$$的危害
防御强,突破点少
记录文件名至数据库
记录IP至数据库
记录当前PHP_SELF至数据库等等
总之就是一些边角料的东西至数据库
二次注入
array_merge引起的任意数据写入
特征明显,但是过滤拦截特严,慢慢找疏忽点吧,绕吧
特征明显,尽力找疏忽点吧
必有,关键点在有无回显,是否支持gopher:// file://等协议
必有,但是价值低
想要摸清鉴权模块,然后细究
在PHP中,通常要配合如sql注入才能控制反序列化内容
#下载文件的地方(前台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;
2.如果限制了文件后缀名,可注释掉后缀名(在文件名存入数据库的时候)。where id=984562 id=../../phpinfo.php--+.png。
#zip绕过 #phar伪协议绕过 include($_GET['path']."inc"); //?path=phar://uploads/u_时间戳_v.png/v.inc
#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\(\$.*\))
#析构函数中出现了函数调用的
public\s+function\s+__destruct\s*\([^)]*\)\s*\{(?:[^{}]*\{[^{}]*\})*[^{}]*\b\w+\s*\((?:[^()]*|\$\{[^}]+\}){1,5}\)\s*;\s*\}
#查找某个方法
function\s+saveDeferred\s*\(
#查找x类中的x方法
class\s+类名\s*\{[\s\S]*?function\s+方法名\(
#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|)\b\s*\(\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;
}
}
}