-
-
[原创]Realworld CTF 2023 The_cult_of_8_bit详解
-
发表于: 2023-1-14 22:53 15553
-
很难的题,
这题要用到的一些知识点
同源策略具体可以参见文档
30cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6i4k6W2L8r3!0H3k6i4u0Q4x3X3g2E0L8%4A6A6L8r3I4S2i4K6u0W2L8%4u0Y4i4K6u0r3P5X3S2Q4x3X3c8o6e0W2)9J5c8X3c8G2j5%4y4Q4x3V1k6i4k6h3u0Q4x3V1k6e0k6h3y4#2M7X3W2@1P5g2)9J5c8W2y4S2L8h3g2Q4x3X3c8G2M7X3W2Y4K9h3&6Q4y4h3k6H3L8$3I4A6j5%4V1`.
这里主要介绍下 跨源脚本 API 访问
允许以下对 Window
属性的跨源访问:
某些浏览器允许访问除上述外更多的属性。
允许以下对 Location
属性的跨源访问:
某些浏览器允许访问除上述外更多的属性。
window
对象表示的就是当前页面,字面意思,是 "根"
可以发现 window
对象的 opener
top
location
frames
focus()
等关键属性和方法我们都是可以跨域访问的,这就为我们后面解题提供了依据。
注意:像上面的那些属性和方法都是 window
对象下的,意味着他们都是全局属性或全局方法,即可以类似这样直接访问,不需要使用 window.
来操作
对于上面的 opener
属性也很有趣,它指向当前窗口的打开者。
即 A 页面使用 open
方法打开了 B 页面,那么 B 页面的 opener
就指向了 A 页面的 window
注意:opener指向的是页面窗口,也就是说你页面的URL再怎么变,opener是不变的
同样,对于 opener
对象内容的访问也要遵循同源策略
jsonp的出现是为了解决一些前端的跨域问题。现浏览器一般情况下都要遵循同源策略,所以跨域传输数据就会比较麻烦,所以出现了jsonp这种东西,相关文档参考 489K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6*7K9s2g2S2L8X3I4S2L8W2)9J5k6i4A6Z5K9h3S2#2i4K6u0W2j5$3!0E0i4K6u0r3M7q4)9J5c8U0t1@1x3K6V1H3y4e0l9&6
简单来说下,就是像<script>
标签是支持跨域的,所以利用 <script>
标签来跨域获取到要执行的方法和参数,前端将获取到的方法和参数添加到 html 的 script
标签中从而解决跨域问题。而这里要说的题就是利用jsop跨域来进行 Same Orign Method Execution Attack (同源方法执行攻击)
这里大力推荐这篇论文,讲的非常详细
019K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2T1L8r3q4U0K9$3S2S2N6q4)9J5k6h3y4G2L8g2)9J5c8X3c8G2j5%4y4Q4x3V1k6W2N6g2)9J5k6o6p5@1i4K6u0r3L8h3q4@1k6i4u0A6j5h3I4K6i4K6u0r3k6i4g2Q4x3X3b7I4y4q4)9J5k6p5S2S2P5h3q4C8i4K6u0V1f1$3q4E0k6g2)9J5k6p5!0J5K9h3N6A6L8W2)9J5k6p5#2W2N6r3S2G2k6q4)9J5k6p5g2^5k6h3y4#2N6r3W2G2L8W2)9J5k6p5g2^5M7r3I4G2K9i4c8A6L8X3N6Q4x3X3c8m8i4K6u0V1b7$3q4D9L8r3u0S2j5$3E0Q4x3X3c8r3L8%4u0Q4x3X3c8e0j5h3#2W2i4K6u0V1e0%4u0A6k6$3W2F1i4K6u0V1f1r3!0D9K9h3y4&6i4K6u0V1b7Y4W2H3j5i4y4K6i4K6u0V1N6%4m8Q4x3X3g2H3k6r3j5`.
这里大致说一下是怎么个回事
比如我前端要跨域获取一些数据,于是我前端可以这样写
dd1K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3g2^5j5h3#2H3L8r3g2Q4x3X3g2U0L8$3#2Q4x3V1j5`.
我们访问 426K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3g2^5j5h3#2H3L8r3g2Q4x3X3g2U0L8$3#2Q4x3V1k6Q4x3@1k6U0j5h3I4D9j5X3q4U0K9#2)9K6c8r3I4G2j5h3c8Q4y4h3k6V1j5i4c8S2
后端 405K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3!0@1K9r3g2J5f1$3W2@1k6g2)9J5k6h3y4G2L8g2)9J5c8X3g2F1k6s2m8G2K9h3&6@1i4K6y4r3j5$3q4D9L8r3u0S2j5$3E0Q4x3@1c8D9L8$3q4V1i4K6g2X3k6r3q4@1j5b7`.`. 访问返回了这样的数据
那么利用jsonp在 f32K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3g2^5j5h3#2H3L8r3g2Q4x3X3g2U0L8$3#2Q4x3V1j5`. 成功跨获取到了 87dK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3!0@1K9r3g2J5f1$3W2@1k6g2)9J5k6h3y4G2L8g2)9J5c8R3`.`. 这个域的数据,并执行 load_data
方法在控制台打印出结果。但如果我们把 callback
的参数改成其他方法,不就可以在前端执行一些方法了吗,虽然一般返回的方法内有参数,但像类似 点击、表单提交、表单输入值篡改等JavaScript 函数,(例如 element.click()
、privateForm.submit()
、inputElement.stepUp/stepDown()
、element.select()
、element.focus ()
、JsDefinedFunction()
、jQueryFunc()
等,这些方法给他参数他也能正常执行。
像上面的例子,我们如果发送这样的url
d8eK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3g2^5j5h3#2H3L8r3g2Q4x3X3g2U0L8$3#2Q4x3V1k6Q4x3@1k6U0j5h3I4D9j5X3q4U0K9#2)9K6c8r3c8G2j5%4g2E0k6h3&6@1i4K6u0W2j5X3!0V1P5g2)9J5k6h3k6A6M7Y4y4@1b7$3S2A6L8r3c8Q4x3X3g2U0L8r3W2U0K9H3`.`.
那么就会发现成功点击了 <a>
标签从而弹窗 "hack"
用法不过多做介绍,这里附上参考文档
650K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6i4k6W2L8r3!0H3k6i4u0Q4x3X3g2E0L8%4A6A6L8r3I4S2i4K6u0W2L8%4u0Y4i4K6u0r3P5X3S2Q4x3X3c8o6e0W2)9J5c8X3c8G2j5%4y4Q4x3V1k6i4k6h3u0Q4x3V1k6m8f1p5W2Q4x3V1k6j5e0f1I4t1N6s2c8H3f1X3g2I4N6h3g2K6N6q4)9J5c8W2g2K6K9h3&6Y4i4K6g2X3h3p5#2x3d9s2c8@1M7q4u0W2M7i4g2W2M7%4b7`.
这里说一些比较少见东西,就是在什么情况下会其 open
方法会报错,发现主要有以下几种情况
关于上面第二条不是有效的 URL 可能有以下几种情况:
例:
iframe参考文档
ac3K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6i4k6W2L8r3!0H3k6i4u0Q4x3X3g2E0L8%4A6A6L8r3I4S2i4K6u0W2L8%4u0Y4i4K6u0r3P5X3S2Q4x3X3c8o6e0W2)9J5c8X3c8G2j5%4y4Q4x3V1k6i4k6h3u0Q4x3V1k6t1g2p5#2x3i4K6u0r3c8h3I4W2L8h3g2F1N6q4)9J5c8X3W2X3M7X3q4E0k6b7`.`.
有关iframe的一些黑魔法
ff8K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2K9s2g2D9K9g2)9J5k6i4c8%4i4K6u0r3x3U0l9J5x3W2)9J5c8U0l9@1i4K6u0r3x3o6N6Q4x3V1k6A6k6Y4u0S2L8h3g2Q4x3X3c8S2L8X3c8Q4x3X3c8%4K9h3&6V1L8%4N6Q4x3X3c8G2M7r3g2F1i4K6u0r3
iframe 就是新开了一个页面,iframe的父子窗口之间使用js交互同样必须遵循 同源策略,若想跨域操作可以用 postMessage
iframe有个 allow
属性,用于为<iframe>
指定其 特征策略 ,所以我们完全可以限制iframe页面的一些功能,像可以禁掉其中的一些api,比如 xhr。
好了,说了这么多终于要回到正题了。
题目给了源码,是一个简单的 expressjs 笔记存储服务,源码很长,需要慢慢审。这里说下关键的地方
首先xssbot是以admin登录后访问我们给的url,没啥好说的。
后端有这么几个路由
可以发现 /
和/post/
者两个页面没有校验 csrftoken和登录情况。
db.js 中
flag对应着post id
所以只要我们知道对应的post id就可以通过 /post/
路由不用登录直接拿到flag。
同时在 /create/post
路由中
可以看到对传来的text进行了过滤,拦截了以 javascript:
开头的 text
这里对应前端 home.ejs
这里的绕过也很容易,给出这样的payload即可绕过
new URL("http://a? onfocus=alert(123) id=x")
不会报错
之后访问 http://localhost:12345/#x 获取到id为x的焦点事件即可xss
但是,注意,这个点在这题并用不上。
首先题目使用了csrftoken,所以没法直接利用CSRF让xssbot向/api/create/todo
发送对应的xss payload来达到目的,其次还有重要的一点是这里
直接限制死了admin不可能向 /api/create/post
和 /api/create/todo
发送数据。
所以走csrf这条路是不行了。
注意前端 post.ejs
这里可以发现,如果 try 捕获到了异常,就会使用 jsonp 技术来获取post相关信息。
后端处理
但前端 post.ejs 中的 POST_SERVER
定死了,我们没法去修改它。
所以可以考虑从查询参数 id
中入手,想办法让我们随心修改 callback 参数从而实现 SOME 攻击。
这里解决的点有两个
解决这两个点有两种不同的方案
思路来自 c80K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6K9o6q4&6L8#2)9J5k6h3q4J5N6q4)9J5c8X3y4@1k6W2)9J5c8Y4c8Z5k6h3y4#2L8s2c8G2k6U0S2T1K9i4c8Q4x3V1j5`.
上文说过,我们可以利用 %00
来让 open
方法报错
像这样的链接
这个链接经过
提取得到
最终经过 encodeURIComponent(id)
方法进行url编码后再拼接就成了
这个url放到 open
方法中正好是会报错的,解决了第一个点。
对于第二个点可以这样构造
不过多解释了,看图就明白了
这样我们就可以随便控制 callback 参数来实现 SOME 攻击了。
思路来自 6b0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2L8h3q4H3L8r3f1K6x3e0b7J5i4K6u0W2L8X3g2@1i4K6u0r3x3U0l9J5x3#2)9J5c8U0l9I4i4K6u0r3x3o6S2Q4x3V1k6J5k6h3q4D9i4K6u0V1N6$3!0J5L8r3c8Q4x3X3c8U0N6r3k6Q4x3X3b7J5x3o6t1K6i4K6u0V1N6%4u0A6N6r3g2#2M7s2y4Q4x3V1k6Q4x3U0y4@1K9r3g2Q4x3X3c8U0N6h3I4@1i4K6u0V1L8$3k6Q4x3X3b7^5i4K6u0V1j5X3W2@1
这个思路也很巧妙
上文也说过,iframe可以通过 allow
属性来设置一些特征策略,其中包括了 sync-xhr
策略,可以在iframe中加载
同时设置其 allow="sync-xhr 'none'"
来禁用 xhr,使得try捕获异常从而跳转使用jsonp访问
这里截获 callback 参数同上,原理是一样的。
test2.html
上面两个方案之后的操作都是配合 SOME 攻击,利用 focus 事件 + iframe来逐位爆破post id。详细见下文题解。
下文用到的知识点是SOME ATTACK,强烈建议先看论文 4e0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2T1L8r3q4U0K9$3S2S2N6q4)9J5k6h3y4G2L8g2)9J5c8X3c8G2j5%4y4Q4x3V1k6W2N6g2)9J5k6o6p5@1i4K6u0r3L8h3q4@1k6i4u0A6j5h3I4K6i4K6u0r3k6i4g2Q4x3X3b7I4y4q4)9J5k6p5S2S2P5h3q4C8i4K6u0V1f1$3q4E0k6g2)9J5k6p5!0J5K9h3N6A6L8W2)9J5k6p5#2W2N6r3S2G2k6q4)9J5k6p5g2^5k6h3y4#2N6r3W2G2L8W2)9J5k6p5g2^5M7r3I4G2K9i4c8A6L8X3N6Q4x3X3c8m8i4K6u0V1b7$3q4D9L8r3u0S2j5$3E0Q4x3X3c8r3L8%4u0Q4x3X3c8e0j5h3#2W2i4K6u0V1e0%4u0A6k6$3W2F1i4K6u0V1f1r3!0D9K9h3y4&6i4K6u0V1b7Y4W2H3j5i4y4K6i4K6u0V1N6%4m8Q4x3X3g2H3k6r3j5`.
紧接着上文方案一,我们创建4个html文件
注意location虽然变了,但opener是不变的
a.html
a.html负责打开 /b.html
和将自身页面重定向到 http://localhost:12345/
b.html
b.html 负责创建 0123456789abcdef-
每个字符的iframe页面,同时创建焦点监听器监听焦点情况,如果监听到某个iframe的焦点则发送该iframe的name。之后打开 /c.html
。注意 b.html 的 opener 指向 a页面(http://localhost:12345/)
c.html
c.html页面的 location受 d.html 页面控制,是个自由页面,用于执行SOME攻击。注意c.html 的 opener 指向 b.html
d.html
注意 d.html 的 opener 指向 c页面。该页面的为主启动页面,利用多级opener,结合SOME攻击和focus事件逐位爆破a页面的post id。
4个页面放服务器上,之后向xss bot发送 122K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0q4Q4x3X3f1K6i4K6y4m8z5o6l9H3x3q4)9J5c8X3q4Q4x3X3g2Z5N6r3#2D9 爆破即可
接上文题目分析的方案二
需要两个页面
index.html
exp.html
同样是利用 SOME ATTACK 和iframe的focus事件来爆破post id。
上述两个方案的题解中有关同源策略的问题可以自己体会体会。
6aaK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6K9o6q4&6L8#2)9J5k6h3q4J5N6q4)9J5c8X3y4@1k6W2)9J5c8Y4c8Z5k6h3y4#2L8s2c8G2k6U0S2T1K9i4c8Q4x3V1j5`.
845K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2L8h3q4H3L8r3f1K6x3e0b7J5i4K6u0W2L8X3g2@1i4K6u0r3x3U0l9J5x3#2)9J5c8U0l9I4i4K6u0r3x3o6S2Q4x3V1k6J5k6h3q4D9i4K6u0V1N6$3!0J5L8r3c8Q4x3X3c8U0N6r3k6Q4x3X3b7J5x3o6t1K6i4K6u0V1N6%4u0A6N6r3g2#2M7s2y4Q4x3V1k6Q4x3U0y4@1K9r3g2Q4x3X3c8U0N6h3I4@1i4K6u0V1L8$3k6Q4x3X3b7^5i4K6u0V1j5X3W2@1
1d3K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2T1L8r3q4U0K9$3S2S2N6q4)9J5k6h3y4G2L8g2)9J5c8X3c8G2j5%4y4Q4x3V1k6W2N6g2)9J5k6o6p5@1i4K6u0r3L8h3q4@1k6i4u0A6j5h3I4K6i4K6u0r3k6i4g2Q4x3X3b7I4y4q4)9J5k6p5S2S2P5h3q4C8i4K6u0V1f1$3q4E0k6g2)9J5k6p5!0J5K9h3N6A6L8W2)9J5k6p5#2W2N6r3S2G2k6q4)9J5k6p5g2^5k6h3y4#2N6r3W2G2L8W2)9J5k6p5g2^5M7r3I4G2K9i4c8A6L8X3N6Q4x3X3c8m8i4K6u0V1b7$3q4D9L8r3u0S2j5$3E0Q4x3X3c8r3L8%4u0Q4x3X3c8e0j5h3#2W2i4K6u0V1e0%4u0A6k6$3W2F1i4K6u0V1f1r3!0D9K9h3y4&6i4K6u0V1b7Y4W2H3j5i4y4K6i4K6u0V1N6%4m8Q4x3X3g2H3k6r3j5`.
51bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6i4k6W2L8r3!0H3k6i4u0Q4x3X3g2E0L8%4A6A6L8r3I4S2i4K6u0W2L8%4u0Y4i4K6u0r3P5X3S2Q4x3X3c8o6e0W2)9J5c8X3c8G2j5%4y4Q4x3V1k6i4k6h3u0Q4x3V1k6e0k6h3y4#2M7X3W2@1P5g2)9J5c8W2y4S2L8h3g2Q4x3X3c8G2M7X3W2Y4K9h3&6Q4y4h3k6H3L8$3I4A6j5%4V1`.
081K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6i4k6W2L8r3!0H3k6i4u0Q4x3X3g2E0L8%4A6A6L8r3I4S2i4K6u0W2L8%4u0Y4i4K6u0r3P5X3S2Q4x3X3c8o6e0W2)9J5c8X3c8G2j5%4y4Q4x3V1k6i4k6h3u0Q4x3V1k6t1g2q4c8b7i4K6u0r3f1r3g2J5L8h3W2K6M7$3W2G2L8Y4y4Q4y4h3k6b7L8$3I4A6j5%4V1`.
4beK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6i4k6W2L8r3!0H3k6i4u0Q4x3X3g2E0L8%4A6A6L8r3I4S2i4K6u0W2L8%4u0Y4i4K6u0r3P5X3S2Q4x3X3c8o6e0W2)9J5c8X3c8G2j5%4y4Q4x3V1k6i4k6h3u0Q4x3V1k6m8f1p5W2Q4x3V1k6i4K9h3&6V1L8%4N6Q4x3V1k6G2M7r3g2F1k6i4t1`.
b62K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6i4k6W2L8r3!0H3k6i4u0Q4x3X3g2E0L8%4A6A6L8r3I4S2i4K6u0W2L8%4u0Y4i4K6u0r3P5X3S2Q4x3X3c8o6e0W2)9J5c8X3c8G2j5%4y4Q4x3V1k6i4k6h3u0Q4x3V1k6t1g2p5#2x3i4K6u0r3c8h3I4W2L8h3g2F1N6q4)9J5c8X3W2X3M7X3q4E0k6b7`.`.
096K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6i4k6W2L8r3!0H3k6i4u0Q4x3X3g2E0L8%4A6A6L8r3I4S2i4K6u0W2L8%4u0Y4i4K6u0r3P5X3S2Q4x3X3c8o6e0W2)9J5c8X3c8G2j5%4y4Q4x3V1k6i4k6h3u0Q4x3V1k6m8f1p5W2Q4x3V1k6j5e0f1I4t1N6s2c8H3f1X3g2I4N6h3g2K6N6q4)9J5c8W2g2K6K9h3&6Y4i4K6g2X3h3p5#2x3d9s2c8@1M7q4u0W2M7i4g2W2M7%4b7`.
属性 | |
---|---|
window.closed |
只读。 |
window.frames |
只读。 |
window.length |
只读。 |
window.location |
读/写。 |
window.opener |
只读。 |
window.parent |
只读。 |
window.self |
只读。 |
window.top |
只读。 |
window.window |
只读。 |
属性 | |
---|---|
HTMLAnchorElement.href |
只写。 |
/
/
将window.location赋值为http:
/
/
example.com,即重定向跳转到http:
/
/
example.com
location
=
"0d4K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3g2^5j5h3#2H3L8r3g2Q4x3X3g2U0L8$3@1`."
;
/
/
获取到当前页面中name为test的iframe对象
let testIframe
=
window[
'test'
];
/
/
或
let testIframe
=
window.frames[
0
];
/
/
获取第
0
个iframe
/
/
将window.location赋值为http:
/
/
example.com,即重定向跳转到http:
/
/
example.com
location
=
"0d4K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3g2^5j5h3#2H3L8r3g2Q4x3X3g2U0L8$3@1`."
;
/
/
获取到当前页面中name为test的iframe对象
let testIframe
=
window[
'test'
];
/
/
或
let testIframe
=
window.frames[
0
];
/
/
获取第
0
个iframe
<html>
<body>
<a href
=
"javascript:alert('hack')"
>hack<
/
a>
<script>
function load_data(data) {
console.log(data[
'data'
]);
}
let callback
=
new URLSearchParams(window.location.search).get(
'callback'
);
let script
=
document.createElement(
"script"
);
script.src
=
"8edK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3!0@1K9r3g2J5f1$3W2@1k6g2)9J5k6h3y4G2L8g2)9J5c8X3g2F1k6s2m8G2K9h3&6@1i4K6y4r3j5$3q4D9L8r3u0S2j5$3E0Q4x3@1b7`."
+
callback;
document.head.appendChild(script);
<
/
script>
<
/
body>
<
/
html>
<html>
<body>
<a href
=
"javascript:alert('hack')"
>hack<
/
a>
<script>
function load_data(data) {
console.log(data[
'data'
]);
}
let callback
=
new URLSearchParams(window.location.search).get(
'callback'
);
let script
=
document.createElement(
"script"
);
script.src
=
"8edK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3!0@1K9r3g2J5f1$3W2@1k6g2)9J5k6h3y4G2L8g2)9J5c8X3g2F1k6s2m8G2K9h3&6@1i4K6y4r3j5$3q4D9L8r3u0S2j5$3E0Q4x3@1b7`."
+
callback;
document.head.appendChild(script);
<
/
script>
<
/
body>
<
/
html>
load_data({
"data"
:
"data"
})
load_data({
"data"
:
"data"
})
/
/
使用非法字符如中间加入空格,
%
00
等
let xhr
=
new XMLHttpRequest();
xhr.
open
(
"GET"
,
"172K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2W2P5l9`.`. ample.com"
);
xhr.send()
/
/
在send时报错
/
/
使用不正确的编码
let xhr
=
new XMLHttpRequest();
xhr.
open
(
"GET"
,
"a2fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2W2P5r3q4E0M7r3I4W2i4K6u0W2j5$3!0E0i4K6u0r3i4K6t1#2x3o6l9`."
);
/
/
在
open
时就会报错
xhr.send()
/
/
使用无效的端口号
let xhr
=
new XMLHttpRequest();
xhr.
open
(
"GET"
,
"https://www.example.com:70000"
);
/
/
在
open
时就会报错
xhr.send()
/
/
使用非法字符如中间加入空格,
%
00
等
let xhr
=
new XMLHttpRequest();
xhr.
open
(
"GET"
,
"172K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2W2P5l9`.`. ample.com"
);
xhr.send()
/
/
在send时报错
/
/
使用不正确的编码
let xhr
=
new XMLHttpRequest();
xhr.
open
(
"GET"
,
"a2fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2W2P5r3q4E0M7r3I4W2i4K6u0W2j5$3!0E0i4K6u0r3i4K6t1#2x3o6l9`."
);
/
/
在
open
时就会报错
xhr.send()
/
/
使用无效的端口号
let xhr
=
new XMLHttpRequest();
xhr.
open
(
"GET"
,
"https://www.example.com:70000"
);
/
/
在
open
时就会报错
xhr.send()
app.get(
"/login/"
, mw.requiresNoLogin, (req, res)
=
> res.render(
"login"
));
app.get(
"/register/"
, mw.requiresNoLogin, (req, res)
=
> res.render(
"register"
));
app.get(
"/report/"
, mw.requiresLogin, (req, res)
=
> res.render(
"report"
));
app.get(
"/post/"
, (req, res)
=
> res.render(
"post"
));
app.get(
"/"
, (req, res)
=
> res.render(
"home"
));
app.get(
"/login/"
, mw.requiresNoLogin, (req, res)
=
> res.render(
"login"
));
app.get(
"/register/"
, mw.requiresNoLogin, (req, res)
=
> res.render(
"register"
));
app.get(
"/report/"
, mw.requiresLogin, (req, res)
=
> res.render(
"report"
));
app.get(
"/post/"
, (req, res)
=
> res.render(
"post"
));
app.get(
"/"
, (req, res)
=
> res.render(
"home"
));
(()
=
> {
let flagId
=
crypto.randomUUID();
console.log(`flag post
ID
: ${flagId}`);
posts.
set
(flagId, {
name:
"Flag"
,
body: process.env.FLAG ||
"flag{test_flag}"
});
users.
set
(
"admin"
,
Object
.freeze({
user:
"admin"
,
pass
: sha256(process.env.ADMIN_PASSWORD ||
"password"
),
posts:
Object
.freeze([flagId]),
todos:
Object
.freeze([])
}));
(()
=
> {
let flagId
=
crypto.randomUUID();
console.log(`flag post
ID
: ${flagId}`);
posts.
set
(flagId, {
name:
"Flag"
,
body: process.env.FLAG ||
"flag{test_flag}"
});
users.
set
(
"admin"
,
Object
.freeze({
user:
"admin"
,
pass
: sha256(process.env.ADMIN_PASSWORD ||
"password"
),
posts:
Object
.freeze([flagId]),
todos:
Object
.freeze([])
}));
router.post(
"/create/todo"
, [mw.csrfProtection, mw.requiresLogin], (req, res)
=
> {
let { text }
=
req.body;
if
(!text) {
return
res.redirect(
"/?msg=Missing text"
);
}
if
(typeof text !
=
=
"string"
) {
return
res.redirect(
"/?msg=Missing text"
);
}
let isURL
=
false;
try
{
new URL(text);
/
/
errors
if
not
valid URL
isURL
=
!text.toLowerCase().trim().startsWith(
"javascript:"
);
/
/
no
} catch {}
req.user.todos.push({
text, isURL
});
res.redirect(
"/"
);
});
router.post(
"/create/todo"
, [mw.csrfProtection, mw.requiresLogin], (req, res)
=
> {
let { text }
=
req.body;
if
(!text) {
return
res.redirect(
"/?msg=Missing text"
);
}
if
(typeof text !
=
=
"string"
) {
return
res.redirect(
"/?msg=Missing text"
);
}
let isURL
=
false;
try
{
new URL(text);
/
/
errors
if
not
valid URL
isURL
=
!text.toLowerCase().trim().startsWith(
"javascript:"
);
/
/
no
} catch {}
req.user.todos.push({
text, isURL
});
res.redirect(
"/"
);
});
<
%
_ user.todos.forEach(todo
=
> { _
%
>
<
%
_
if
(todo.isURL) { _
%
>
<li
class
=
"has-text-left"
><a target
=
"_blank"
href
=
<
%
=
todo.text
%
>><
%
=
todo.text
%
><
/
a><
/
li>
<
%
_ }
else
{ _
%
>
<li
class
=
"has-text-left"
><
%
=
todo.text
%
><
/
li>
<
%
_ } _
%
>
<
%
_ }); _
%
>
<
%
_ user.todos.forEach(todo
=
> { _
%
>
<
%
_
if
(todo.isURL) { _
%
>
<li
class
=
"has-text-left"
><a target
=
"_blank"
href
=
<
%
=
todo.text
%
>><
%
=
todo.text
%
><
/
a><
/
li>
<
%
_ }
else
{ _
%
>
<li
class
=
"has-text-left"
><
%
=
todo.text
%
><
/
li>
<
%
_ } _
%
>
<
%
_ }); _
%
>
http:
/
/
a? onfocus
=
alert(
123
)
id
=
x
http:
/
/
a? onfocus
=
alert(
123
)
id
=
x
/
/
Don't allow admin to make new posts
/
todos
router.use((req, res,
next
)
=
> {
if
(req.user.user
=
=
=
"admin"
) {
return
res.redirect(
"/?msg=Nice try"
);
}
next
();
});
/
/
Don't allow admin to make new posts
/
todos
router.use((req, res,
next
)
=
> {
if
(req.user.user
=
=
=
"admin"
) {
return
res.redirect(
"/?msg=Nice try"
);
}
next
();
});
window.onload
=
function() {
const
id
=
new URLSearchParams(window.location.search).get(
'id'
);
if
(!
id
) {
return
;
}
/
/
Load post
from
POST_SERVER
/
/
Since POST_SERVER might be a different origin, this also supports loading data through JSONP
const request
=
new XMLHttpRequest();
try
{
request.
open
(
'GET'
, POST_SERVER
+
`
/
api
/
post
/
`
+
encodeURIComponent(
id
), false);
request.send(null);
}
catch (err) {
/
/
POST_SERVER
is
on another origin, so let's use JSONP
let script
=
document.createElement(
"script"
);
script.src
=
`${POST_SERVER}
/
api
/
post
/
${
id
}?callback
=
load_post`;
document.head.appendChild(script);
return
;
}
load_post(JSON.parse(request.responseText));
}
window.onload
=
function() {
const
id
=
new URLSearchParams(window.location.search).get(
'id'
);
if
(!
id
) {
return
;