邓永凯
邓永凯,绿盟科技安全工程师,主要负责绿盟科技的系统和web漏扫开发工作,并担任攻防产品攻防代表,后加入研究院做web和IoT安全研究的工作,电子杂志《安全参考》、《书安》创办人。
邓永凯:大家下午好。很荣幸跟大家探讨关于安全开发的问题。为什么取这个名字《那些年,你怎么写总会出现的漏洞》?今天是安全开发者大会,在座各位基本上都是开发为主的,我们在开发的过程当中不仅仅要把功能完善好,也要注意在开发过程中的安全问题,是不是你就会在你的代码里面埋下一些雷。
今天主要从这三个方面给大家展开来讲,利用大量案例讲解初级程序员、高级程序员以及疯狂程序员,他们在开发编码的过程当中是怎样把漏洞写出来的。为什么我加了防御、做了过滤、漏洞已经修复还是不断被黑客攻击?
初级程序员开发的时候基本上不会考虑安全问题,因为他只要把任务完成了,功能开发OK了,上线运行OK就可以了,他不会考虑到安全漏洞,或者没有安全开发的概念。
很简单,直接获取参数进到数据库操作里面,一个赤裸裸的注入。
$id
= $_POST[ 'id' ]; // Check database 上下文无过滤处理 $query = "SELECT name
FROM users WHERE user_id = 通过GPC获取参数 '$id';"; $result = mysql_query(
$query ) or die(mysql_error() ); 直接带入数据库操作
直接把参数拼接到系统命令里面,执行命令里面,很暴力的命令注入。
$target
= $REQUEST[ 'ip' ]; 上下文无过滤处理 $cmd = 'ping -c 4 ' . $target; 通过GPC获取参数
$result = shellexec($cmd); //直接带入执行命令 直接拼接系统命令 $html .= "
{$cmd}
"; 最后带入命令执行函数
另外文件上传,直接获取文件上传无任何判断。
$target_path
= ROOT . "hackable/uploads/"; $target_path .= basename( $_FILES[
'uploaded' ][ 'name' ] ); //上下文无过滤处理,直接上传任意文件 //无判断直接上传文件
move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path );
这些简单例子功能上来看没有问题,我可以查阅数据,可以执行命令,可以上传文件,但是有一个共同的缺陷就是,它获取数据之后根本没有考虑到用户数据是正常数据还是恶意数据,如果传入恶意数据那么这些地方就会被黑客利用,那么你的业务功能也就会存在问题。
初级开发者就会说:“我把东西只要写完了,功能已经OK,上线正常运行了,你现在跟我说安全开发,什么是安全编码?我只是个写代码的啊,你要我怎样?”。
高级程序员经验非常丰富做过很多项目,有很多的经验。对用户的数据进行一些处理,或者是加一些过滤,给业务里面加一些软WAF等。但是,虽然做了这些安全措施,如果不到位或者是不完整或者是错误的方法,错误的修复或者是加过滤了,结果是一样的仍然存在漏洞。
比如获取参数了,加入数据库之前进行了处理,这个时候把用户数据过滤了,但是在数据库操作时这个ID没有单双引号的保护,这个过滤有什么用?过滤跟没有过滤不是一个样子吗?
无单引号保护:
$id
= $POST[ 'id' ]; //$id = addslashes( $id ); $id =
mysqlrealescapestring( $id ); // Check database $query = "SELECT name
FROM users WHERE userid = $id;"; $result = mysqlquery( $query ) or
die(mysql_error() );
//mysqlrealescape_string过滤 或者addslashes过滤 最后带入数据库操作
一些经验的开发者在代码全域入口加上软WAF服务,通过一些请求包把获得的参数统一过滤了,都可转义了,而且变量的数据库进到数据里面加上一些引号保护,也就是说想来注入,必须绕过我的单双引号,这个时候就没有办法了,感觉很完美!
非GPC获取参数:
foreach(array('COOKIE',
'POST', 'GET') as $request) { foreach($$request as $key => $value) {
$key{0} != '' && $$key = addslashes($value); $M['form'][$key] =
addslashes($value); } } //软WAF: 全局addslashes过滤GPC,全局将变量双引号保护
万事大吉,完美!!!???
别忘了获取数据不仅仅通过GET、POST、COOKIE,通过别的方式也可以获取,比如FILES、SERVER,前面就没有考虑。
非GPC获取参数:
$pseudourl=$SERVER[REQUESTURI];
$dirs=explode('/',$pseudourl); $dirdirname=$dirs[count($dirs)-2];
$query="select * from setting where name='$dirdirname'";
$jump=$db->get_one($query);
//$SERVER[] $FILE[],不受全局软WAF影响
上传这个文件的考虑你的文件有单双引号吗,这样就不受全局软WAF的影响。通过count做一些信息带到库里面,虽然软AWF服务,但是这个里面Select根本不受你的控制。
很多时候程序员在写代码的时候会经过各种各样的处理,在进行核心操作的时候比如说查询数据库或者进入数据库的时候会给你一个反编码,因为他害怕里面有一些他不想要的东西,比如说不合法的东西,他先给你反编码一下,或者说他干脆给你一个反转义,比如说反转义,那么前面处理转义已经没有用了,而且进入数据库的时候跟他有一个反编码、反转义,那提交数据的时候编码不就行了吗,多来几次编码,编码之后就没有恶意数据了,提交数据后你再反转义反编码不照样恶意数据就还原回去了。
编码等:
$pseudourl=
addslashes ($POST[‘url’]); $dirdirname =urldecode($pseudourl); //
$dirdirname =base64decode($pseudourl); // $dirdirname
=stripslashes($pseudo_url); $query="select * from setting where
name='$dirdirname'"; $jump=$db->getone($query);
//URL编码绕过 Base64编码绕过 反转义绕过 ......
还有其他的案例,它想到了安全问题,你要逃逸单引号,我把你变成两个单引号,单引号成对出现之后就没有逃逸功能,就没有办法注入了。我输入单引号你给我编两个单一号,我输入一个斜杠单引号,但是斜杠并没有处理,斜杠是一个转义的的功能。(如果数据库用的是postgresql,就没有办法绕过了,因为它每个这个反斜线。不同的数据库也有不同的效果。)
替换:
String
assetIp = Request(‘address'); String conditionSql = "and (e.sip='"
+assetIp.replaceAll("'", "''") +"')"; //String querySql = ......
Conn.Execute(querySql );
//Mysql: \' Bypass //Postgresql: No Bypass
下面这个案例是一个意料之外的问题。这个都进入到那个prepare里面,把里面’%s’,”%s”统一替换为’%s’,要注入首先逃逸单引号,但是前面做了处理逃逸单引号是不可能的,所以是没有问题的。
格式化字符串:
$A
= prepare(" AND metavalue = %s", $value1); $B = prepare("SELECT * FROM
table WHERE key = %s $A", $value2); //'%s' 和 "%s"替换为'%s' function
prepare( $query, $args ) { $query = strreplace( "'%s'", '%s', $query );
//Bypass必须逃逸单引号 $query = str_replace( '"%s"', '%s', $query ); $query =
pregreplace( '|(?
'|(?
这样来看可能是没有问题,他所有的东西都包括进来了,而且对方想要逃逸单双引号是不可能,恰恰是格式化的时候就有一个问题,比如说这是一个合法格式化的东西,格式化的内容。
格式化字符串:
输入:
1
%1$%s (here sqli payload) -- , dump AND metavalue = %s AND metavalue =
'1 %1$%s (here sqli payload) --' SELECT * FROM table WHERE key = %s AND
metavalue = '1 %1$'%s' (here sqli payload) --' SELECT * FROM table WHERE
key = 'dump' AND metavalue = '1 _dump' (here sqli payload) --'
/* %1$'%s 1 第一个参数 $类型 ' 附加值padding % padding字符 */
第一次进入prepare函数之后进行一个替换,变成下面的一个,这还是’%s’,再替换一下,有一些数据库拼接起来,所以这个地方红色标入的地方都有一个单引号。但是那个单引号最后被吃掉了,为什么中间单引号不见了,这就是格式化字串的东西。
这个东西1就是代表了格式化第一个类型,后面那个单引号是干什么,是格式化时候的一个附加值用来做padding的,比如说单引号后面必须有一个字符,如果这个字符不够会以单引号后面的另外一个字符附加进去。附加多少也是在后面一个数字,如果你不给数字的话就返回空,比如说这个里面单引号%S,就是不加任何东西反馈一个空,这个时候恰恰那个里面一个单引号就被当成Padding功能干掉了,这个时候就逃逸单引号进行注入了,这是一种绕过方法。
格式化字符串:
输 :空格%s空格, array(_dump, OR 1=1 --)
AND metavalue = %s AND metavalue = ' %s '
SELECT
* FROM table WHERE key = %s AND metavalue = ' %s ' SELECT * FROM table
WHERE key = '%s' AND metavalue = ' '%s' ' SELECT * FROM table WHERE key =
'dump' AND metavalue = ' ' OR 1=1 -- ' '
另外一种绕过方法,可以传入空格+%s+空格,第一次替换变成这样,这样会不会有问题?如果第一个%S被拒了是不是就直接执行了?这个是合法,而且可以成功执行。看起来是非常正常而且考虑很多功能的代码,还是会产生很多的问题,也有很多办法可以绕过。
做项目的时候有这样一个案例,在前台找到注入管理的帐号,但是管理员没有办法登陆,因为找不到后台,前台的功能有限,所以做渗透测试的时候拿不到shell都是耍流氓。你找不到后台,前台没有办法让你登陆,你就只能看代码。前台限制登陆的时候,有这样一个代码。如果你登录的用户名是admin就直接退出了,前台登不了,后台也登不了,这样写大家看看是不是没有问题,但是这里存在一个登录绕过。
字符集:
$mysqli->query("set
names utf8"); $username = addslashes($_GET['username']); if ($username
== "admin"){ die("not loing use admin"); } $sql = "SELECT *
FROM users WHERE user='{$username}'";
//无法找到后台 前台禁止admin登录 //程序员: 我不让你admin用户登录,你还能奈我何?
Myspl存储数据的时候有格式,首先是字符集用什么语言存储,哪种比对格式,Ci,大小写不敏感。那么ADMIN前面判断不等admin,但是查询出来这个结果是一样的。这样ADMIN就绕过了。
字符集:
绕过方法一 http://localhost/kanxue.php?username=AdmiN
Mysql存储字符的格式: 字符集语言比对方式 例如:utf8generalci/cs/bin ci=case insensitive 大小写不敏感 绕过判断!
第二种情况,刚刚前面也有一句话代码set names utf8,这个功能是什么,将这个客户端所有的字符集都变为utf8,服务端模拟为latin1,这字符集有差异,有差异的时候就会进行这个字符集的转换,转换的过程中就有这个错误的编码字符。继续绕过!
字符集:
绕过方法二: http://localhost/kanxue.php?username=admin%D3
set names utf8 客户端(php mysqli)字符集utf8 服务端(mysql)字符集latin1 字符集转换导致绕过 绕过判断!
获取一个用户名,获取一个密码,密码Md5加密,看你用户名密码对不对,这是很简单的几句话。刚刚开始看的时候我也没发现有什么问题,但是你注意到这个MD5有两个参数,一个是字符串,一个输出格式。第二个参数默认为False。如果为True就是以16位原始二进制字符返回,如果是False就是32位十六进制数。如果输入129等等一大串之后,返回的内容里面有一个单引号’Or’8其他字符,这个时候相当于一个万能密码。
为什么呢,把后面那个密码带到那个密码之后,’Or’8其他字符就相当于万能密码了,而且这个O单引号的后面必须是数字开头,这个时候查询出来的数据是OK的,相当于一个万能密码绕过。如果’Or’8其他字符后面是字母或者非数字的话就不行了,因为’Or’的字符是数字开头则永远返回True,这个时候就绕过了。可以写一个脚本爆破一下,只要有一个单引号数字开头的就可以了,就可以绕过了。
md5:
$username
= mysqlrealescapestring ($POST['username']); $password =
md5($POST['password'], TRUE); $query = "select *
from userswhere username= '$username' and password='$password'"; $result
= $db->getone($query); if ($result == TRUE ){ echo "login success"; }
//登录绕过
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2020-2-6 16:18
被kanxue编辑
,原因: