セキュリティを学習するなら自分で自身を攻撃し、自ら防いで学べ
構成:Spring Boot,Thymeleaf
まずは、呟き画面を作って、urlが含まれていればurl化にするアプリを作る
コントローラ.java
/**
* CSRFのテスト
* @param model
* @param formList
* @return
*/
@GetMapping("/twi")
public String twiA(Model model, @RequestParam(name = "formList", required = false, defaultValue = "") List<String> formList) {
List<String> list = new ArrayList<>();
list.addAll(formList);
model.addAttribute("formList", list);
return "twi";
}
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Hello</title>
</head>
<body>
<h1>呟きリスト</h1>
<form action="twi">
<span><input type="text" name="formList"></span>
<button type="submit">呟く</button><br>
<th:block th:each="list ,stat :${formList}">
<input type="hidden" th:value="${list}" name="formList">
<p th:id="${stat.count}">[[${list}]]</p>
</th:block>
</form>
<script>
//URL化
const twi = document.getElementsByName('formList');
for(let i = 0; i < twi.length ; i++){
//'https://~.com~'をURLとみなす。
const pattern1 = /^(https|http)(:\/\/[a-z]{1,}\.com.*)/g;
const result1 = pattern1.test(twi[i].value);
if (result1) {
//要素1の中にあるURLをリンクに置き換える
const text = document.getElementsByName('formList')[i].value;
const url = text.replace(pattern1, "<a href='$1$2' target='_blank'>$1$2</a>");
document.getElementById(i).innerHTML = url;
}
}
</script>
</body>
</html>
参考:[JavaScript]サブドメイン許可 url 正規表現 チェック
セッション認証が組み込まれている想定で攻撃
ログインユーザがリンクを押下したら勝手に投稿された。
セッションIDでチェックしていても本人のセッション情報なので意味がない。
コントローラ.java
@Autowired
HttpSession session;
/**
* CSRFのテスト
*
* @param model
* @param formList
* @return
*/
@GetMapping("/twi")
public String twiA(Model model,
@RequestParam(name = "formList", required = false, defaultValue = "") List<String> formList,
HttpServletRequest request) {
session = request.getSession();
//session認証 sessionIdが違っていれば呟けない処理がここにあるとする
//sessionチェック・・・
List<String> list = new ArrayList<>();
list.addAll(formList);
model.addAttribute("formList", list);
return "twi";
}
}
twi.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Hello</title>
</head>
<body>
<h1>呟きリスト</h1>
<p>id:[[${#httpSession.id}]]</p>
<p>コピペ用:http://localhost:8080/twi?formList=乗っ取り</p>
<form action="twi">
<span><input type="text" name="formList"></span>
<button type="submit">呟く</button><br>
<th:block th:each="list ,stat :${formList}">
<input type="hidden" th:value="${list}" name="formList">
<p th:id="${stat.count}">[[${list}]]</p>
</th:block>
</form>
<script>
//URL化
const twi = document.getElementsByName('formList');
for(let i = 0; i < twi.length ; i++){
//'https://~'をURLとみなす。
const pattern1 = /^(https|http)(:\/\/[a-z]{1,}.*)/g;
const result1 = pattern1.test(twi[i].value);
if (result1) {
//要素1の中にあるURLをリンクに置き換える
const text = document.getElementsByName('formList')[i].value;
const url = text.replace(pattern1, "<a href='$1$2' target='_blank'>$1$2</a>");
document.getElementById(i).innerHTML = url;
}
}
</script>
</body>
</html>
CSRF対策をする
攻撃者に予測困難なhiddenパラメータでチェックする。
ユーザーにhiddenパラメータを持たせる→投稿→サーバー側でhiddenパラメータが変更されてないかチェック。
スパムリンク押下だとhiddenパラメータが送信されてこない。もちろん画面を覗かれれば終わるが、それは以前の問題。
攻撃者が乗っ取りスパムリンクを投稿
トークンチェックにより弾かれる。
//session認証 sessionIdが違っていれば呟けない処理がここにあるとする
//sessionチェック・・・
List<String> list = new ArrayList<>();
//呟きがある場合、トークンが一致しないと呟かない
+ if(!formList.isEmpty()) {
+ if(token == 9999) {
list.addAll(formList);
model.addAttribute("formList", list);
+ } else {
+ list.add("トークンチェックにより呟けませんでした");
+ model.addAttribute("formList", list);
+ }
+ }
+ model.addAttribute("token", 9999);
return "twi";
}
<body>
<h1>呟きリスト</h1>
<p>id:[[${#httpSession.id}]]</p>
<p>コピペ用:http://localhost:8080/twi?formList=乗っ取り</p>
+ <p>token:[[${token}]]</p>
<form action="twi">
<span><input type="text" name="formList"></span>
<button type="submit">呟く</button><br>
<th:block th:each="list ,stat :${formList}">
<input type="hidden" th:value="${list}" name="formList">
<p th:id="${stat.count}">[[${list}]]</p>
</th:block>
+ <input type="hidden" th:value="${token}" name="token">
</form>
参考:セッション
参考:Springにおけるセッション管理3パターン
参考:Springにおけるセッション管理(主に@SessionAttributes使用パターン)
参考:Spring SecurityでCSRF対策