软件安全实验 SEEDlabs 跨站请求伪造攻击实验
环境设置
容器设置
起docker
1 | |

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

Task1:观察HTTP请求
在跨站请求伪造攻击中,我们需要伪造HTTP请求。因此,我们需要知道一个合法的HTTP请求是什 么样子的,以及它使用什么参数等。为此,我们可以使用一个名为”HTTP Header Live”的火狐浏览器插件。本任务的目标是熟悉这个工具。指导中给出了如何使用这个工具的说明(§5.1)。
请使用这个工具在 Elgg 中捕获一个HTTPGET请求和一个HTTPPOST请求。请在你的报告中指出这些请求中所使用的参数。
简单来说,就是用HTTP Header Live抓GET、POST包。
旧版本的firefox里的HTTP Header Live不能用了

这里下了一个新版本的Firefox,安装HTTP Header Live插件
随便在百度搜索,抓到一个GET包

请求头如下
1 | |
URL:
1 | |
GET传递的参数就是
1 | |
然后再某旅行网站搜索,抓到一个POST包

请求头如下
1 | |
POST传递的参数为
1 | |
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

然后看一下添加好友是怎么实现的:
点进 Alice 主页,发现Add friend功能。那我们点击Add friend,同时别忘了用HTTP Header Live抓包

抓到包之后,我们看一下

我们(Samy)添加Alice为好友这一行为,实际就是发送了一个URL:
1 | |
题目描述提到:
我们已经禁用了本实验中的防御措施,所以你不需要在伪造的请求中包括这两个参数(
__elgg_ts和__elgg_token)
所以这里我们就看这部分就可以了
1 | |
可以看到,加好友用的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

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

那我们自己的用户 id 为 59,所以Alice访问http://www.seed-server.com/action/friends/add?friend=59就可以加我们为好友
题目是想让我们构建www.attacker32.com网页的内容,从而让Alice访问该网页时添加我们为好友
提示:你可以使用 img标签,这将自动触发一个HTTP GET请求
img是一个HTML标签,用来在网页上插入图片。
就像写文章时会贴上一张照片,网页想显示图片时就写<img>这个标签,把图片的地址告诉它。
1 | |
- src(source):图片的地址,在网上或你电脑上的位置。
- alt(alternative text):当图片无法显示时,用文字说明图片内容,或给视障人士阅读器用。
比如查看这个网站图片的源代码,

会发现是一个img标签
1 | |
其中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:

1 | |
然后我们用老版本的firefox登录Alice的账号,访问http://www.attacker32.com/,并点击`Add-Friend Attack`链接
这里新Firefox登录Samy,便于HTTP Header Live抓包;旧Firefox登录Alice账号,用以模拟Alice的行为。

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

这里有个问题,就是上面我们使用老版本firefox做的,为什么不用最新版做呢?
部分版本firefox其实是不能成功的,比如最新版,我们可以按F12看一下网络:
当我们进行请求时,会发现访问http://www.seed-server.com/的包被拦截了,而且是跳转到了/login路由,并非我们写的http://www.seed-server.com/action/friends/add?friend=59

原理下面会提到
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 | |
在了解了请求的结构后,你需要从你的攻击性网页上使用JavaScript代码生成请求。为了帮助你编写 这样一个JavaScript程序,我们在下面中提供了一个示例代码。你可以使用这个示例代码来构建你的恶意 网站从而进行CSRF攻击。这只是一个示例代码,你需要修改它来使其适用于你的攻击。
1 | |
在行中,值2的含义是将一个字段的访问级别设置为public。如果没有设置这个值,则访问级别将 被默认设置为私有,那么其他人将无法看到这个字段。应该注意的是,当从PDF文件中复制和粘贴上述代码时,程序中的单引号字符可能会变成别的字符(但看起来仍然是单引号)。这将导致语法错误。将所 有的单引号符号替换为从键盘上输入的单引号符号就可以解决这些错误。
和任务三差不多,其实就是改用POST传参
首先我们看看修改个人资料是如何实现的:先找到修改个人资料的地方,修改后保存

抓包如下
1 | |
接口是/action/profile/edit,参数很多
然后我们补充上面攻击代码,实现攻击,其实补充下面几个部分就行
1 | |
编辑 editprofile.html即可

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

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

跳转,显示修改成功

问题 除了详细描述你的攻击外,你还需要在实验报告中回答如下问题:
问题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 | |
同时Elgg将秘密令牌和时间戳的值赋值给JavaScript变量,因此它们可以很容易地被同一页面上的 JavaScript 代码获得。
1 | |
秘密令牌和时间戳由 vendor/elgg/elgg/views/default/input/securitytoken.php 模块添加到 Elgg 的网页中。下面的代码片段显示了它们是如何被动态地添加到网页中。
1 | |
秘密令牌生成Elgg的秘密令牌是一个哈希值(MD5信息摘要),这个哈希值是由该网站上的秘密值(从 数据库检索得到)、时间戳、用户会话ID和随机生成的会话字符串所共同生成。下面的代码显示了Elgg中 秘密令牌的生成过程(在vendor/elgg/elgg/engine/classes/Elgg/Security/Csrf.php中)。
秘密令牌验证elgg网络应用程序验证了生成的令牌和时间戳,以防御CSRF攻击。每个用户操作都会调 用Csrf.php中的validate函数,该函数的作用就是验证令牌。如果令牌不存在或无效,该用户操作将 被拒绝,且用户将被重定向。在我们的设置中,我们在这个函数的开头添加了一个return,因此该验证 程序被禁用。
1 | |
Task:开启防御措施要打开防御措施,首先要进入Elgg容器的/var/www/elgg/vendor/elgg/elgg/engine/classes/Elgg/Security文件夹,从Csrf.php中删除return语句。你可以使用一个内置在容器中的简单编辑器,名为nano。在做了修改之后再次重复之前的攻击,看看你的攻击是否会成功。请指出捕获的HTTP请求中的秘密令牌,并解释为什么攻击者为什么不能在CSRF攻击中发送这些秘密令牌; 是什么阻止了他们从网页上发现秘密令牌?
应该注意的是(重要),当我们发起编辑资料攻击的时候,失败的尝试将导致攻击者的页面被重新加载。这将会再次触发伪造的POST请求,从而导致另一次失败的尝试,那么页面又将再次被重新加载,再 次触发伪造的POST请求。这种无休止的循环会拖慢你的计算机。因此,在验证了攻击失败后,你需要关 闭该标签以停止无休止的循环。
我们先用nano改代码,注释掉下面这句
1 | |

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


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

什么攻击者不能伪造合法的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

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

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

再点link A,这是cross-site request
也是GET和POST两种传参方式
GET传参:
cookie-normal和cookie-lax传递;cookie-strict不传递

POST传参:
cookie-normal传递;cookie-lax和cookie-strict不传递

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 类型丢了,很可能是跨站点发起(比如其他网站诱导你向本网站发起请求)。
- 结合
Referer和Origin头部,服务器可以进一步判断请求来源,但 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 是否存在且匹配。