16 KiB
1. SQL注入漏洞
SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
1.1. SQL注入漏洞-普通注入
SQL注入经常出现在登录页面、获取HTTP头(user-agent/client-ip等)、订单处理等地方,因为这几个地方是业务相对复杂的登录页面的注入大多是发生在 HTTP头里面的client-ip和x-forward-for, 一般用来记录登录的IP地址。在订单系统里面,由于订单涉及购物车等多个交互,所以经常会发生二次注入。我们在通读代码挖掘漏洞的时候可以着重关注这几个地方。
<?php
$uid = $_GET['id'];
$sql = "SELECT * FROM userinfo where id=$uid";
$conn = mysql_connect('localhost','root','root');
mysql_select_db("test",$conn);
$result = mysql_query($sql,$conn);
print_r('当前SQL语句:' . $sql . '<br />结果:');
@$row = mysql_fetch_row($result);
if($row){
echo '正确';
}else{
echo mysql_error($conn);
}
@print_r($row);
// var_dump(mysql_fetch_row($result));
mysql_close();
?>
分析上述代码存在的问题?
注入代码:
http://localhost:8081/2020CodeAudit/C4/4-1-1-1-SQL.php?id=-1 union select 1,user(),3,4
1.2. 编码注入
程序在进行一些操作之前经常会进行一些编码处理,而做编码处理的函数也是存在问题的,通过输入转码函数不兼容的特殊字符,可以导致输出的字符变成有害数据, 在SQL注入里,最常见的编码注入是MySQL宽字节以及urldecode/rawurldecode函数导致的。
1.2.1. 宽字节注入
单字节字符集:所有的字符都使用一个字节来表示,比如 ASCII 编码(0-127)多字节字符集:在多字节字符集中,字节用多个字节来表示。UTF-8 编码: 是一种多字节编码,它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度常见的宽字节: GB2312、GBK、GB18030、BIG5、Shift_JIS宽字节注入时利用mysql的一个特性,使用GBK编码的时候,会认为两个字符是一个汉字。
在使用PHP连接MySQL的时候,当设置“set character_set_client=gbk”时会导致一个编码转换的注入问题,也就是我们所熟悉的宽字节注入,当存在宽字节注入漏洞时,注入参数里带入%df%27('),即可把程序中过滤的\ (%5c)''吃''掉。
假设/1.php?id=1里面的id参数存在宽字节注入漏洞,当提交
?id=-1' and 1=1%23
%23 是16进制,表示特殊字符#
那么MySQL 运行的 SQL 语句为:
select * from user where id= ' -1\' and 1 = 1#'
addslashes函数:
返回需要在转义字符之前添加反斜线的字符串。这些字符是:
- 单引号(
')- 双引号(
")- 反斜线(
\)- NUL(NUL 字节)
很明显这是没有注入成功的,我们提交的单引号被转义导致没有闭合前面的单引号。
但是我们提交:
?id=-1 %df' and 1=1%23
第一步:单引号转义:
-1 %df\' and 1=1%23(#)
第二步
-1 %df%5c' and 1=1#
最后组装成的SQL是合法的:
select * from user where id='-1運' and 1=1#'
这是由于单引号被自动转义成',前面的%df和转义字符\反斜杠组合成 %df%5c,也就是“運”字,这时候单引号依然还在,于是成功闭合了前面的单引号。
问题:这里的%23有什么用?
<?php
$conn = mysql_connect('localhost','root','root');
mysql_select_db("test",$conn);
mysql_query("SET NAMES 'gbk'",$conn);
$uid = addslashes($_GET['id']);
$sql = "SELECT * FROM userinfo where id='$uid'";
$result = mysql_query($sql,$conn);
print_r('当前SQL语句:' . $sql . '<br />结果:');
print_r(mysql_fetch_row($result));
mysql_close();
?>
http://localhost:8081/2020CodeAudit/C4/4-1-1-2-SQL-GBK.php?id=%df' union select 1,2,user(),4%23
运行结果:
当前SQL语句:SELECT * FROM userinfo where id='運\\' union select 1,2,user(),4#'
结果:Array ( [0] => 1 [1] => 2 [2] => root@localhost [3] => 4 )
注意,为什么是'運\\'有两个斜杠?可能你的PHP环境中配置了 magic_quotes_gpc ,会自动为GET或者是POST提交的数据前面增加反斜杠转义。
防范:
- 在执行查询之前先执行 SET NAMES ‘gbk’,character_set_client=binary
- 使用 mysql_set_charset('gbk')设置编码,然后使用 mysql_real_escape_string() 函数过滤参数。
- 使用PDO方式,在PHP5.3.6及以下版本需要设置setAttribute(PDO::ATTR_EMULATE_PREPARES, false);来禁用 prepared statements 的仿真效果。
- 使用UTF8等编码。
1.2.2. 二次 urldecode 注入
只要字符被进行转换就有可能产生该漏洞现在的Web程序大多都会进行参数过滤,GPC如果某处使用了urldecode或者rawurldecode函数,则会导致二次解码生成单引号而引发注入。
原理:提交参数到Webserver时,Webserver会自动解码一次,假设目标程序开启了GPC提交/l.php?id=l%2527,因为提交的参数里面没有单引号,所以第一次解码后的结果是id=l%27, %25解码的结果是%,如果程序里使用了urldecode或者rawurldecode函数来解码id参数,则解码后的结果是 id=1'。单引号成功出现引发注入。
<?php
$a = addslashes($_GET['p']);
$b = urldecode($a);
echo '$a='.$a;
echo '<br />';
echo '$b='.$b;
?>
http://localhost:8081/2020CodeAudit/C4/4-1-1-2-SQL-urlcode.php?p=1%2527
- %25 对应字符 %
这个时候就出现了问题。PHP首先会对传递过后的URL进行自动解码,解码后得到的请求字符串是:
p=1%27
这时如果再使用 urldecode ,相当于对url进行了二次解码,%27再解码表示字符 ' 。因此该漏洞叫做二次 urldecode 注入,其实是解码了两次。
因此运行结果:
$a=1%27
$b=1'
注意:代码中的 addslashes 函数其实没有作用,因为url中没有包含需要 addslashes 处理的字符。
1.2.3. espcms搜索注入分析
漏洞在 interface/search.php 文件和 interface/3gwap_search.php 文件 in_taglist()函数都存在。
in_taglist() 的部分代码:
$page = $this->fun->accept('page', 'G');
$page = isset($page) ? intval($page) : 1;
$lng = (admin_LNG == 'big5') ? $this->CON['is_lancode'] : admin_LNG;
$tagkey = urldecode($this->fun->accept('tagkey', 'R'));
$tagkey = $this->fun->inputcodetrim($tagkey);
跟踪 fun->accept 函数,发现使用了addslashes对数据进行处理,然后有使用了urldecode对数据解码,可能存在二次urldecode注入。
二次漏洞分析参考 注意,该分析的版本与我们用的源码不一致,不能浮现,原理可以看看。
http://localhost:8081/espcms/index.php?ac=search&at=taglist&tagkey=a%2527,tags) or 1=1-- ss
执行后,可以看到选择出来的数据库:
1.3. 漏洞防范
- 在执行查询之前先执行 SET NAMES ‘gbk’,character_set_client=binary
- 使用 mysql_set_charset('gbk')设置编码,然后使用 mysql_real_escape_string() 函数过滤参数。
- 使用PDO方式,在PHP5.3.6及以下版本需要设置setAttribute(PDO::ATTR_EMULATE_PREPARES, false);来禁用 prepared statements 的仿真效果。
- 使用UTF8等编码。
2. XSS漏洞
挖掘XSS漏洞的关键在于寻找没有被过滤的参数,且这些参数传入到输出函数,常用的输出函数列表如下:print、print_r、echo、printf、sprintf、die、 var_dump、var_ export最重要的还要掌握各种浏览器容错、编码等特性和数据协议。反射型、存储型和DOM型。
骑士cms存储型XSS分析
/admin/admin_link.php 部分代码:
// 。。。
require_once(ADMIN_ROOT_PATH.'include/admin_link_fun.php');
// 。。。
$act = !empty($_GET['act']) ? trim($_GET['act']) : 'list';
$smarty->assign('pageheader',"友情链接");
if($act == 'list')
{
// 。。。
$link = get_links($offset, $perpage,$joinsql.$wheresql.$oederbysql);
$smarty->assign('link',$link);
$smarty->assign('page',$page->show(3));
$smarty->assign('upfiles_dir',$upfiles_dir);
$smarty->assign('get_link_category',get_link_category());
$smarty->assign('navlabel',"list");
$smarty->display('link/admin_link.htm');
}
判断访问admin_link.php这个文件的时候有没有act参数,没有就给act变量赋值为list,进入到输出友情链接列表的代码。
get_links函数定义位于admin/include/admin_link_fun.php中。从数据库读取友情链接列表。返回后直接进行模板渲染。
第54行代码:$smarty->display('link/admin_link.htm');将读取的内容以link/admin_link.htm为模板显示出来。跟进模板页admin/templates/default/link/admin_link.htm
get_links 函数:
function get_links($offset, $perpage, $get_sql= '')
{
global $db;
$row_arr = array();
$limit=" LIMIT ".$offset.','.$perpage;
$result = $db->query("SELECT l.*,c.categoryname FROM ".table('link')." AS l ".$get_sql.$limit);
while($row = $db->fetch_array($result))
{
$row_arr[] = $row;
}
return $row_arr;
}
显然这个函数直接从数据库中查询结果后返回,并没有进行任何的标签或者是特殊字符的转义处理。
跟踪 admin/templates/default/link/admin_link.htm 文件,大约在63行:
{#if $list.link_logo<>""#}
<span style="color:#FF6600" title="<img src={#$list.link_logo#} border=0/>" class="vtip">[logo]</span>
{#/if#}
这段代码是有问题的,这里直接把显示logo的img标签放在span标签的title里面,当鼠标滑过的时候会调用事件执行显示title即执行img标签这里的利用点是 {#$list.link_logo#}可以是HTML实体编码,从而绕过骑士cms的安全检査。
通过连接提交代码:
http://localhost/74cms/link/add_link.php
logo地址输入:
1 onerror=alert(1)
“r ;“是字母r的HTML实体编码。
其实意思是:
1 onerror=alert(1)
onerror可以用于不同的对象,比如window对象、img元素或者其他资源加载的元素。当这些对象发生错误时,会触发onerror事件。例如,当图片加载失败时,img的onerror事件会被触发,可以执行一些回调函数,比如替换图片或者记录错误。
另外,onerror还可以用于资源加载,比如link、script、img等标签。例如,当script标签加载失败时,onerror事件会被触发,可以用来动态加载备用脚本。
登录后台:
http://localhost/74cms/admin/admin_index.php
鼠标移动到 logo 显示1的弹出窗口,注入成功!
按F12,打开调试界面中的元素:
可以看到生产的html源代码:
<span style="color:#FF6600" title="<img src=1 onerror=alert(1) border=0/>" class="vtip">[logo]</span>
2.1. XSS漏洞防范
- 过滤掉相关的特殊字符
- 标签事件属性黑白名单
- 输出编码:htmlspecialchars()函数
- 防御DOM XSS:
- 避免客户端敏感操作
- 分析和强化客户端的JavaScript代码
- Anti-XSS
- HttpOnly
3. CSRF漏洞(跨站请求伪造)
CSRF主要是用于越权操作所有漏洞自然在有权限控制的地方,像管理后台、会员中心、论坛帖子以及交易管理等从白盒角度来说,只要读代码的时候看看几个核心文件里面有没有验证token和referer相关的代码这里的核心文件指的是被大量文件引用的基础文件。
3.1. Discuz CSRF备份拖库分析
漏洞文件在 source/admincp/admincp_db.php第30行开始。
$backupdir = C::t('common_setting')->fetch('backupdir'); // line 28
if(!$backupdir) {
$backupdir = random(6);
@mkdir('./data/backup_'.$backupdir, 0777);
C::t('common_setting')->update('backupdir',$backupdir);
}
$backupdir = 'backup_'.$backupdir;
if(!is_dir('./data/'.$backupdir)) {
mkdir('./data/'.$backupdir, 0777);
文件夹名是六位随机数。
搜索整个文档,发现所有的参数都是通过 GET 获取,而且并没在管理页面随机生成令牌。也就是说只要管理员登录后,从任意页面访问这个页面的合法URL就可以备份文件。那就可以在论坛页面嵌入一个URL,当管理员不注意点中后,就可以备份数据库。而且数据库文件是在web目录中,并没有经过保护,通过爆破url就可以下载这个文件。
$backupfilename = './data/'.$backupdir.'/'.str_replace(array('/', '\\', '.', "'"), '', $_GET['filename']);
这里是真实的备份文件名,直接通过GET参数获取,不用爆破文件名了,只需要爆破6位的目录名,简单了很多。
在论坛发帖,写入漏洞地址:
http://localhost/discuz/admin.php?action=db&operation=export&setup=1&scrolltop=&anchor=&type=custom&customtables[]=pre_ucenter_admins&method=multivol&sizelimit=2048&extendins=0&sqlcompat=&usehex=1&usezip=0&filename=xinan&exportsubmit=%CC%E1%BD%BB22
注意,URL地址中有个参数是你需要备份的数据表的名字,请确认和你的安装数据表名是一致的。
思考:如何得到漏洞的访问地址?
3.2. CSRF漏洞防范
- 使用POST提交用户数据,来代替GET
- 使用验证码:每次用户提交内容时,都要求其在表单中填写图片上的随机验证码,并且在提交表单后对其进行检测使用请
- 求令牌Token:在HTTP请求中以参数的形式加入一个随机产生的请求令牌,并在服务器端对其进行验证。如果请求中没有Token或者Token的内容不正确,则认为可能是CSRF攻击而拒绝该请求。
3.3. 什么是Token令牌
Token翻译中文为“标志”,在计算机认证领域叫令牌。利用验证Token的方式是目前使用的最多的一种,也是效果最好的一种可以简单理解成在页面或者cookie里加一个不可预测的字符串,服务器在接收操作请求的时候只要验证下这个字符串是不是上次访问留下的即可判断是不是可信请求,因为如果没有访问上一个页面,是无法得到这个Token的,除非结合XSS漏洞或者有其他手段能获得通信数据。
3.3.1. 一个简单的例子
<?php
session_start();
function set_token(){
$_SESSION['token'] = md5(time()+rand(1,1000));
}
function check_token(){
if(isset($_POST['token']) && $_POST['token'] === $_SESSION['token']){
return true;
}
else{
return false;
}
}
if(isset($_SESSION['token']) && check_token()){
echo "success";
}
else{
echo "failed";
}
set_token();
?>
<form method="post">
<input type="hidden" name="token" value="<?=$_SESSION['token']?>">
<input type="submit"/>
</form>
http://localhost/2020CodeAudit/C4/token/4-3-2-1-Token.php
问题:什么是session,工作原理是什么?cookie 又是什么?session 和 cookie 有什么联系?
todo: 可能需要补充 cookie 和 session 的知识!



