要回答您的问题 - 验证为 System.Uri 是不够的。我解决问题的方法:
- 不允许任何引号字符或换行符,也许您可能需要在查询字符串中接受引号,但现在我完全禁止它们。如果我真的需要它,我可能会改变主意。
- 如果检测到任何实体(例如
&),则不允许 - 拒绝该 url
- 如果字符串解析为 Uri - 检查方案是否被接受
此时 url 仍可能类似于 javascript&#58alert(1) - 浏览器将接受 &#58 而 WebUtility.HtmlDecode 由于缺少分号而失败,所以:
- 如果在字符串的查询字符串部分之前有一个 & 符号或冒号 - 拒绝它
这里是代码。
private static readonly string[] acceptedSchemes = { Uri.UriSchemeHttp, Uri.UriSchemeHttps, Uri.UriSchemeMailto, Uri.UriSchemeFile };
private static readonly char[] forbiddenHrefChars = { '"', '\'', '`', (char)10, (char)13 };
private static readonly char[] forbiddenBeforeQueryString = { ':', '&', '\\' }; // the colon may look surprising, but if we deal with a colon we expect something which parses as URI
/// <summary>
/// Returns true if the specified string is considered XSS safe href attribute value.
/// </summary>
public static bool IsSafeHref(string input)
{
if (input.Any(c => forbiddenHrefChars.Contains(c)))
return false;
// do not accept any entities
string href = WebUtility.HtmlDecode(input);
if (href != input)
return false;
// check if the scheme is valid, if specified
bool isUri = Uri.TryCreate(input, UriKind.Absolute, out Uri uri);
if (uri != null)
return acceptedSchemes.Contains(uri.Scheme ?? "");
int qsIdx = href.IndexOf('?');
string partBeforeQueryString = qsIdx < 0 ? href : href.Substring(0, qsIdx);
if (forbiddenBeforeQueryString.Any(c => partBeforeQueryString.Contains(c)))
return false;
return true;
}
我相信它不应该允许以任何方式切换 URL 的上下文以作为 javascript 执行。如果你找到破解它的方法,请告诉我。