对于跨站问题也就是我们常说的存储、反射和 DOM 三种,在挖掘 XSS 上我们一般的思路是查找可能在页面上进行输出的变量,并检查这些变量是否可控,然后跟踪传递过程,查看后端处理是否对接收的内容进行过滤或编码,在前端显示时是否被 htmlencode 之类的函数做过处理。
对于很多系统,不建议过滤特殊字符,一个体验不佳再一个有些系统是有需求可以输入特殊符号的,这时后台可以进行编码处理,例如 asp 的 Server.HTMLEncode,php 的 htmlspecialchars,java 的 org.apache.commons.lang.StringEscapeUtils 等。这里需要注意的是后台如果对收到的内容整体进行编码,例如汉子编码后就会多出很多字节,会浪费空间,建议根据实际情况进行处理,可以只过滤特殊符号这种,同时前台输出时也应用进行转义处理。
这里我们以 php 为例简单的看个例子,就拿我们常说的 bluecms1.6.sp1 为例,存在反射跨站的是根目录的 ad_js.php 文件,其源码如下:
<?php
$ad_id = !empty($_GET['ad_id']) ? trim($_GET['ad_id']) : '';
if(empty($ad_id)){
echo 'Error!';
exit();
}
$ad = $db->getone("SELECT * FROM ".table('ad')." WHERE ad_id =".$ad_id);
if($ad['time_set'] == 0){
$ad_content = $ad['content'];
}else{
if($ad['end_time'] < time()){
$ad_content = $ad['exp_content'];
}else{
$ad_content = $ad['content'];
}
}
$ad_content = str_replace('"', '\"',$ad_content);
$ad_content = str_replace("\r", "\\r",$ad_content);
$ad_content = str_replace("\n", "\\n",$ad_content);
echo "<!--\r\ndocument.write(\"".$ad_content."\");\r\n-->\r\n";
?>
上面源码是获取了 ad_id 的值,然后字节拼接到了 sql 语句中,这里首先就已经存在了注入问题,当给 ad_id 赋一个字符字符串之类的东西时,ad 的内容就变成了 sql 执行语句错误的内容,最后会字节输出到页面,这时插入的恶意 js 语句等也会进行输出并执行。需要注意的是,代码中的正则替换只匹配了 "/r/n,而并没有 <> 符。
存储我们还拿 bluecms 做示例,在 bluecms 中的 common.fun.php 文件中有一个获取 ip 的函数,其中使用了 getenv 来获取客户端的 ip 地址,但并没有对 ip 进行过滤,可伪造,函数代码如下。
function getip()
{
if (getenv('HTTP_CLIENT_IP')){
$ip = getenv('HTTP_CLIENT_IP');
}
elseif (getenv('HTTP_X_FORWARDED_FOR'))
{ //获取客户端用代理服务器访问时的真实ip 地址
$ip = getenv('HTTP_X_FORWARDED_FOR');
}
elseif (getenv('HTTP_X_FORWARDED'))
{
$ip = getenv('HTTP_X_FORWARDED');
}
elseif (getenv('HTTP_FORWARDED_FOR'))
{
$ip = getenv('HTTP_FORWARDED_FOR');
}
elseif (getenv('HTTP_FORWARDED'))
{
$ip = getenv('HTTP_FORWARDED');
}else{
$ip = $_SERVER['REMOTE_ADDR'];
}
return $ip;
}
以上 getenv 获取的变量都是客户端的 ip,这里可以抓包修改数据包头中的 ip 值,而获取 ip 这个函数在程序中的评论功能有使用,其部分代码如下。
if(empty($content)){
showmsg('评论内容不能为空');
}
if($_CFG['comment_is_check'] == 0){
$is_check = 1;
}else{
$is_check = 0;
}
$sql = "INSERT INTO ".table('comment')." (com_id, post_id, user_id, type, mood, content, pub_date, ip, is_check) VALUES ('', '$id', '$user_id', '$type', '$mood', '$content', '$timestamp', '".getip()."', '$is_check')";
$db->query($sql);
这里主要的就是 insert 语句,ip 字段用的值就是 getip 函数,因为 ip 并没有做严格的判断和限制,其构造 payload 如下。
00','1'),('','1','0','1','6',('<script>alert(1)</script>'),'8888888888','99
以上代码放到 sql 语句的 ip 值位置时,整个 sql 语句就变成了插入两条数据,而第二条的 content 字段也就是内容我们放的 js 代码,这时访问被评论的文章时,js 代码便会执行。这里需要注意的是 content 内容字段是被经过过滤的,所以无法插入恶意脚本,而我们便利用的 ip 字段问题来构造 sql 成功插入了恶意代码。
DOM 简单来说就是客户端的脚本程序可以通过 DOM 动态的检查和修改页面内容,他不会依赖服务器的数据,而是从客户端的 DOM 中获取数据并在本地执行,获取的这些数据若没有进行过适当的过滤和消毒,则可能会造成 DOMXSS。在检测 DOMXSS 时,需要多注意一些输入源以及可以触发 DOM 的属性,例如 document.referer,window.name,location 等。这里我们列一个简单的示例,示例如下。
<html>
<head>
<title>DOMXSS</title>
</head>
<body>
<script>
var a = document.URL;
document.write(a.substring(a.indexOF(a="")+2,a.length));
</scirpt>
</body>
</html>
以上页面其 js 通过 document 获取 url 并把 a 的值显示在页面上,而当 a 的值为恶意的脚本代码时,其页面输出便会被执行。下面再列举一个 document.referer 的例子,示例如下。
<html>
<head>
<title>DOMXSS</title>
</head>
<body>
<a href="http://127.0.0.1/domxss.html">domxss</a>
来自:<script>document.write(document.referrer);</script>
</body>
</html>
当访问 127.0.0.1/domxss.html?<script>alert(1)</script>时,点击 domxss 会进行页面跳转,这里示例就还跳到页面本身,但 referrer 会记录上一个的页面地址,即从哪里跳过来的并打印,而上一个地址我们最后添加了恶意脚本,所以这里会被执行。同样的,例如一些 windows.name,location 等属性也都有可能造成 domxsss,例如以下示例。
<html>
<head>
<title>DOMXSS</title>
</head>
<body>
<script>
document.write("site:" + document.location.href);
</script>
</body>
</html>
location 是 js 用来管理 url 的一个内置对象,示例中的 location.href 会获取 url 地址,而当我们访问 127.0.0.1/domxss.html?#<script>alert(1)</script>时,其恶意代码便会执行。
这里我们会发现 domxss 其实和反射 xss 有点像,domxss 也可以算作是反射 xss 的一种,都是通过 url 去控制和触发的,区别在于,dom 是页面输出,和服务器没有交互,像反射可能与服务器有交互,例如搜索功能的关键字会在页面打印而被触发,其此操作交互了服务器。
挖掘 domxss,主要需要关注数据的输入和输出,例如刚刚的输入 document.referer,document.name,location 等,输出例如 innerHTML,document.write 等。不论 dom,反射和存储也一样,其流程大体都是跟踪输出的函数变量,找出可能危害的地方,然后回溯变量和函数的调用过程,查看用户是否可以控制输入。
对于跨站审计流程我们可以跟踪输出的函数变量,找出可能危害的地方,然后回溯变量和函数的调用过程,查看用户是否可以控制输入。对于渗透测试只要有输入的地方就有可能存在,对于防护建议如果程序允许,可以在入库的时候就过滤掉特殊字符,如果程序需要特殊字符的存在,则在出库显示页面时需要根据输出的位置来做好编码,例如 url 编码,页面显示编码,或者是作为属性的编码。同时要做好关键字过滤,可以使用正则匹配,如果存在便签且里面有 on 开头的内容则应该都被过滤掉。
因为跨站类型多且很多功能一不小心就会存在此问题,所以跨站漏洞也比较多,且危害高,在审计和渗透时应尽可能的仔细审计和测试,尽量避免此问题的发生。