クロスサイトリクエストフォージェリ（CSRF）のテスト

セキュリティを学習するなら自分で自身を攻撃し、自ら防いで学べ

構成：Spring Boot,Thymeleaf

# まずは、呟き画面を作って、urlが含まれていればurl化にするアプリを作る

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384409/98349f79-7459-119f-35f4-d8a014f8c78b.png)
↓
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384409/e233d140-e11f-d7f2-83e6-d2b05ca79af8.png)



```コントローラ.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";
	}

```

```html
<!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 正規表現 チェック

https://qiita.com/sachiko-kame/items/deabe7618899f8c64f7f

# セッション認証が組み込まれている想定で攻撃

攻撃者が乗っ取りスパムリンクを投稿
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384409/ce6e5728-b94c-0cf4-7f09-54ad44569820.png)

ログインユーザがリンクを押下したら勝手に投稿された。
セッションIDでチェックしていても本人のセッション情報なので意味がない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384409/0ddadaa9-9656-e518-0585-249abf20c141.png)


```コントローラ.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パラメータが送信されてこない。もちろん画面を覗かれれば終わるが、それは以前の問題。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384409/a33c0080-c1f2-58cd-0c0e-51c29d104791.png)

攻撃者が乗っ取りスパムリンクを投稿

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384409/3777e1b5-d130-a43b-fc75-067572870a3f.png)

トークンチェックにより弾かれる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384409/886e782e-94d0-0d5a-b0d3-40464a2eb7e2.png)

```diff_java
		//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";
	}
```

```diff_html
<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>
```

---
参考：セッション

https://zenn.dev/shimotaroo/articles/4650c0ad73b08a

参考：Springにおけるセッション管理3パターン

https://www.memory-lovers.blog/entry/2018/02/04/171013

参考：Springにおけるセッション管理（主に@SessionAttributes使用パターン）

http://terasolunaorg.github.io/guideline/current/ja/ArchitectureInDetail/WebApplicationDetail/SessionManagement.html

---
参考：Spring SecurityでCSRF対策

http://terasolunaorg.github.io/guideline/current/ja/Security/CSRF.html