文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 2019-11-09   3.4k

Web安全学习之XSS漏洞实战学习

  1. 编写一个存在 xss 漏洞的页面
  2. 利用 xss 漏洞获取当前用户 cookie
  3. 思考,利用 xss 漏洞能干嘛(参考 beef)
  4. 扩展学习:学习如何防御 xss、总结防御策略加到报告里

要求:记录编写的页面代码、操作流程、思考总结

0x01 XSS漏洞

XSS(Cross Site Scripting)跨站脚本攻击,也是一种注入攻击,当web应用对用户输入过滤不严格,攻击者写入恶意的脚本代码(HTML、JavaScript)到网页中时,如果用户访问了含有恶意代码的页面,恶意脚本就会被浏览器解析执行导致用户被攻击。

常见的危害有:cookie窃取,session劫持,钓鱼攻击,蠕虫,ddos等。

0x02 XSS漏洞分类和代码案例

反射型XSS

原理

反射型xss一般出现在URL参数中及网站搜索栏中,由于需要点击包含恶意代码的URL才可以触发,并且只能触发一次,所以也被称”非持久性xss”。

代码案例

  1. Low Level

    漏洞代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?php

    // Is there any input?
    if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Feedback for end user
    echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
    }
    setcookie("Cookie", "Test");
    ?>

    漏洞利用:

  2. Medium Level

    漏洞代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?php

    if (array_key_exists("name", $_GET) && $_GET['name'] != NULL) {
    $name = str_replace('<script>', '', $_GET['name']);

    echo "<pre>Hello {$name}</pre>";
    }
    setcookie("Medium", "CookieTest");

    ?>

    漏洞利用:

    • 双写<script>标签:<s<script>cript>
    • 标签转换大小写:<SCRipt>

  3. High Level

    漏洞代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?php

    // Is there any input?
    if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Get input
    $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );

    // Feedback for end user
    echo "<pre>Hello ${name}</pre>";
    }

    ?>

    漏洞利用:

    这里使用了正则表达式过滤了<script>标签, 这时候不论是大小写、双层<script>都无法绕过,此时可以使用别的标签,比如<img>

    payload: <img src=0 onerror=alert(document.cookie)>

存储型XSS

原理

允许用户提交数据的Web应用程序都有可能会出现存储型XSS漏洞,当攻击者提交一段XSS代码后,被服务器接收并存储,当攻击者再次访问某个页面时,这段XSS代码被程序读出来响应给浏览器,造成XSS跨站攻击,这就是存储型XSS。

代码案例

  1. Low Level

    前端页面代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <form method="post" name="guestform" action="low.php">
    <table width="550" border="0" cellpadding="2" cellspacing="1">
    <tr>
    <td width="100">Name *</td> <td>
    <input name="txtName" type="text" size="30" maxlength="10"></td>
    </tr>
    <tr>
    <td width="100">Message *</td> <td>
    <textarea name="mtxMessage" cols="50" rows="3" maxlength="50"></textarea></td>
    </tr>
    <tr>
    <td width="100">&nbsp;</td>
    <td>
    <input name="btnSign" type="submit" value="Sign Guestbook"></td>
    </tr>
    </table>
    </form>

    服务器端代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    <?php

    error_reporting(E_ALL);

    if(isset($_POST['btnSign']))
    {

    $servername = "localhost";
    $username = "root";
    $password = "root";
    $dbname = "security";

    // 创建连接
    $conn = new mysqli($servername, $username, $password, $dbname);
    // 检测连接
    if ($conn->connect_error) {
    die("数据库连接失败: " . $conn->connect_error);
    } else
    echo "数据库连接成功";

    echo "<hr>";

    echo $_POST['mtxMessage'];

    $message = trim( $_POST[ 'mtxMessage' ] );
    $name = trim( $_POST[ 'txtName' ] );

    $message = stripslashes( $message );
    $message = mysqli_real_escape_string( $message );

    $name = mysqli_real_escape_string( $name );

    $sql = "INSERT INTO guestbook (comment,name) VALUES ('$message','$name');";

    echo "SQL插入语句:".$sql;

    echo "<hr>";

    if ($conn->query($sql) === TRUE) {
    echo "新记录插入成功";
    } else {
    echo "Error: " . $sql . "<br>" . $conn->error;
    }

    $conn->close();
    }
    ?>

    相关函数介绍:

    trim(string,charlist)函数移除字符串两侧的空白字符或其他预定义字符,预定义字符包括、\t、\n、\x0B、\r以及空格,可选参数charlist支持添加额外需要删除的字符。

    mysqli_real_escape_string(string,connection)函数会对字符串中的特殊符号(\x00,\n,\r,\,‘,“,\x1a)进行转义。

    stripslashes(string)函数删除字符串中的反斜杠。

    可以看到,对输入并没有做XSS方面的过滤与检查,且存储在数据库中,因此这里存在明显的存储型XSS漏洞。

    Message字段注入XSS代码:

    由于Name字段在前端有字数限制,可以直接使用Burp Suite抓包修改参数。

  2. Medium Level

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    <?php

    error_reporting(E_ALL);

    if(isset($_POST['btnSign']))
    {

    $servername = "localhost";
    $username = "root";
    $password = "root";
    $dbname = "security";

    // 创建连接
    $conn = new mysqli($servername, $username, $password, $dbname);
    // 检测连接
    if ($conn->connect_error) {
    die("连接失败: " . $conn->connect_error);
    } else
    echo "数据库连接成功";

    echo "<hr>";

    echo $_POST['mtxMessage'];

    $message = trim( $_POST[ 'mtxMessage' ] );
    $name = trim( $_POST[ 'txtName' ] );

    $message = strip_tags( addslashes( $message ) );
    $message = mysqli_real_escape_string( $message );
    $message = htmlspecialchars( $message );

    $name = str_replace( '<script>', '', $name );
    $name = mysqli_real_escape_string( $name );

    $sql = "INSERT INTO guestbook (comment,name) VALUES ('$message','$name');";

    echo "SQL插入语句:".$sql;

    echo "<hr>";

    //$result = mysqli_query($query) or die('<pre>' . mysql_error() . '</pre>' );

    if ($conn->query($sql) === TRUE) {
    echo "新记录插入成功";
    } else {
    echo "Error: " . $sql . "<br>" . $conn->error;
    }

    $conn->close();
    }

    ?>

    相关函数介绍:

    htmlspecialchars:将特殊字符转换为 HTML 实体

    字符 替换后
    & &amp;
    " &quot;,除非设置了 ENT_NOQUOTES
    设置了 ENT_QUOTES 后, &#039;(如果是 ENT_HTML401) ,或者 &apos;(如果是 ENT_XML1ENT_XHTMLENT_HTML5)。
    < &lt;
    > &gt;

    可以看到,由于对message参数使用了htmlspecialchars函数进行编码,因此无法再通过message参数注入XSS代码,但是对于name参数,只是简单过滤了<script>字符串,仍然存在存储型的XSS。跟反射型XSS一样,可以抓包修改参数使用双写绕过大小写混淆绕过

  3. High Level

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    <?php

    error_reporting(E_ALL);

    if(isset($_POST['btnSign']))
    {

    $servername = "localhost";
    $username = "root";
    $password = "root";
    $dbname = "security";

    // 创建连接
    $conn = new mysqli($servername, $username, $password, $dbname);
    // 检测连接
    if ($conn->connect_error) {
    die("连接失败: " . $conn->connect_error);
    } else
    echo "数据库连接成功";

    echo "<hr>";

    echo $_POST['mtxMessage'];

    $message = trim( $_POST[ 'mtxMessage' ] );
    $name = trim( $_POST[ 'txtName' ] );

    $message = strip_tags( addslashes( $message ) );
    $message = mysqli_real_escape_string( $message );
    $message = htmlspecialchars( $message );

    $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
    $name = mysqli_real_escape_string( $name );

    $sql = "INSERT INTO guestbook (comment,name) VALUES ('$message','$name');";

    echo "SQL插入语句:".$sql;

    echo "<hr>";

    if ($conn->query($sql) === TRUE) {
    echo "新记录插入成功";
    } else {
    echo "Error: " . $sql . "<br>" . $conn->error;
    }

    $conn->close();
    }

    ?>

    可以看到,这里使用正则表达式过滤了<script>标签,但是却忽略了imgiframe等其它危险的标签,因此name参数依旧存在存储型XSS。 同样抓包修改name参数为 <img src=1 onerror=alert(\xss\)>

DOM型XSS

原理

用户请求一个经过专门设计的URL,它由攻击者提交,而且其中包含XSS代码。服务器的响应不会以任何形式包含攻击者的脚本。当用户的浏览器处理这个响应时,DOM对象就会处理XSS代码,导致存在XSS漏洞。

由于DOM是在客户端修改节点的,所以基于DOM型的XSS漏洞不需要于服务器端交互,它只发生在客户端处理数据的阶段。

代码案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<title>Test DOM_XSS</title>
<script type="text/javascript">
function replace(){
document.getElementById("id1").innerHTML = document.getElementById("dom_input").value;
}
</script>
</head>
<body>
<center>
<h6 id="id1">这里会显示输入的内容</h6>
<form action="" method="post">
<input type="text" id="dom_input" value="输入"><br>
<input type="button" value="替换" onclick="replace()">
</form>
<hr>
</center>

</body>
</html>

0x03 XSS漏洞防御

因为XSS漏洞涉及输入和输出两个部分,所以其修复也分为两种。

  • 过滤输入的数据,包括'’,"<>on*等非法字符。
  • 对输入到页面的数据进行相应的编码转换,包括HTML实体编码、Javascript编码等。

HttpOnly

HttpOnly主要防御的是XSS漏洞中的Cookie劫持,浏览器禁止页面的JavaScript访问带有HttpOnly属性的Cookie。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<? php
setcookie("cookie1", "test1", NULL, NULL, NULL, NULL, FALSE);
setcookie("cookie2", "test2", NULL, NULL, NULL, NULL, TRUE);
?>

<html>
<body>
<script type="text/javascript">
alert(document.cookie);
</script>

</body>
</html>

这段代码中,cookie1没有HttpOnly,cookie2被标记为HttpOnly。

输入检查(XSS Filter)

在XSS的防御上,输入检查一般是检查用户输入的数据中是否包含一些特殊字符,如<、>、’、"等。如果发现存在特殊字符,则将这些字符过滤或者编码。比较智能的输入检查还会匹配XSS的特征,比如查找用户数据中是否包含了<script>javascript等敏感字符。而且输入检查的逻辑必须放在服务器端代码实现。

但是XSS Filter有一个问题,就是对"<"、">"等字符的处理,可能会改变用户输入数据的语义。并且,输入的数据会被展示在多个地方,每个地方的语境各不相同,例如:

用户输入的昵称如下:

1
2
$nickname = '我是"天才"';

如果被XSS Filter转义后:

1
2
$nickname = '我是\"天才\"';

如果在HTML代码中展示:

1
2
<div>我是\"天才\"</div>

如果在JavaScript代码展示:

1
2
3
var nick = '我是\"天才\"';
document.write(nick);

输出检查

一般来说,除了富文本的输出,在变量输入到HTML页面时,可以使用编码或转义的方式来防御XSS攻击。

HTMLEncode

针对HTML代码的编码方式是HtmlEncode,在HtmlEncode中要求至少转换:&<>"'\

在PHP中,有htmlentities()htmlspecialchars()两个函数可以满足安全要求。

JavaScriptEncode

JavaScriptEncode需要使用\对特殊字符进行转义,而且在对抗XSS漏洞时,还要求输出的变量必须在引号内部,例如:

1
2
3
var x = escapeJavaScript($evil);
var y = '"'+escapeJavaScript($evil)+'"';

如果输入的是1; alert(2),则:

1
2
3
var x = 1; alert(2);
var y = "1; alert(2)";

第一行执行了额外的代码,第二行则是安全的。

或者也可以使用更加严格的JavaScriptEncode函数来保证安全——除了数字和字母外的所有字符,都使用十六进制\xHH的方式进行编码,例如:

1
2
var x = 1\x3balert\x282\x29;

具体实例

在HTML标签中输出

1
2
3
<div>$var</div>
<a href=#>$var</a>

这种情况一般是构造一个<script>标签,payload:

1
2
3
<div><script>alert(xss)</script></div>
<a href=#><img src=# onerror=alert(1)></a>

防御方式是对变量使用HtmlEncode。

在HTML属性中输出

1
2
<div id="abc" name="$var"></div>

payload:

1
2
3
4
<div id="abc" name="">
<script>alert(1)</script><"">
</div>>

防御方式也是采用HtmlEncode。

在<script>标签中输出

在<script>标签中输出,首先应该确保输出的变量在引号中:

1
2
3
4
<script>
var x = "$var";
</script>

攻击者首先要先闭合引号才能实施XSS攻击:

1
2
3
4
<script>
var x = "";alert(/xss/);//";
</script>

防御时使用JavaScriptEncode。

在事件中输出

1
2
<a href=# onclick="funcA('$var')" >test</a>

payload:var = '); alert(/xss/);//

即:

1
2
<a href=# onclick="funcA(''); alert(/xss/);//')" >test</a>

防御时使用JavaScriptEncode编码。

在CSS中输出

一般来说,尽可能禁止用户可控制的变量在”<style>标签“、”HTML标签的style属性“以及”CSS文件“中输出。

在地址中输出

一个URL组成如下:

1
2
[Protocal][Host][Path][Search][Hash]

例如:

1
2
3
4
5
6
7
https://www.evil.com/a/b/c/?abc=123#ssss
[Protocal] = "https://"
[Host] = "www.evil.com"
[Path] = "/a/b/c/"
[Search] = "?abc=123"
[Hash] = "#ssss"

一般来说,在URL的path(路径)或者search(参数)中输出,使用URLEncode即可。URLEncode会将字符转换为”%HH“形式。

但在Protocal和Host中不能使用严格的URLEncode编码,因为会把://.等都编码掉。

1
2
<a href="$var" >Test</a>

攻击者可以使用伪协议实施攻击:

1
2
<a href="javascript:alert(1);">Test</a>

对于Mozilla支持的dataURI伪协议,它能够将一段代码写在URI中,例如:

1
2
<a href="dataURI:test/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></a>

这段代买的意思时,以test/html的格式加载编码为base64的数据。加载完成后实际上时:

1
2
<script>alert(1)</script>

对于这种手段的攻击,首先应该检查变量是否是以http开头,如果不是则自动加上,再对变量进行URLEncode,即可保证没有此类的XSS攻击。

Copyright © ca01h 2019-2020 | 本站总访问量