๊ทธ ์ ๋ช ํ ๋ฐ์ด๋ฌ์ค ๋ฉ์ผ, ํดํน ๋ฉ์ผ, ์ฌ์ดํธ์ ๋ํ์ ์ธ ๊ณต๊ฒฉ ๋ฐฉ๋ฒ์ด๋ค. ํด๋ผ์ด์ธํธ๊ฐ ์ธ์ฆ๋ ์ํ์ผ ๋ ์ด๋ ํ ๋ฐฉ์์ด๋ ๊ณต๊ฒฉ ์ฝ๋๋ฅผ ์คํํ๊ฒ ๋ง๋ค์ด์ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ์กฐ์ํ๊ฒ ๋ง๋ ๋ค.
์ฌ์ดํธ ๊ฐ ์์ฒญ ์์กฐ(๋๋ ํฌ๋ก์ค ์ฌ์ดํธ ์์ฒญ ์์กฐ, ์์ด: Cross-site request forgery, CSRF, XSRF)๋ ์น์ฌ์ดํธ ์ทจ์ฝ์ ๊ณต๊ฒฉ์ ํ๋๋ก, ์ฌ์ฉ์๊ฐ ์์ ์ ์์ง์๋ ๋ฌด๊ดํ๊ฒ ๊ณต๊ฒฉ์๊ฐ ์๋ํ ํ์(์์ , ์ญ์ , ๋ฑ๋ก ๋ฑ)๋ฅผ ํน์ ์น์ฌ์ดํธ์ ์์ฒญํ๊ฒ ํ๋ ๊ณต๊ฒฉ์ ๋งํ๋ค.์ ๋ช ๊ฒฝ๋งค ์ฌ์ดํธ์ธ ์ฅ์ ์์ ๋ฐ์ํ ๊ฐ์ธ์ ๋ณด ์ ์ถ ์ฌ๊ฑด์์ ์ฌ์ฉ๋ ๊ณต๊ฒฉ ๋ฐฉ์ ์ค ํ๋๋ค.
๋ง์ผ, A๋ผ๋ ์ฌ์ดํธ์ ์ฌ์ฉ์ ๊ฐ์ธ ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ์ ํ๋ ์ฃผ์ ํจํด์ด
abc.com/user.do?cmd=user_passwd_change&user=admin&newPwd=1234
๋ผ๊ณ ํ๋ค๋ฉด ์ด๋ฌํ ๋งํฌ๋ฅผ ์ฌ์ฉ์์ ๋ฉ์ผ๋ก ๋ณด๋ด๋๋ฐ, ๋ง์ฝ ์ฌ์ฉ์๊ฐ ๋ฉ์ผ์ ์ฝ๊ฒ ๋๋ฉด ํด๋น ์ฌ์ฉ์์ ํจ์ค์๋๊ฐ 1234๋ก ์ด๊ธฐํ๋๋ค. ์ด๋ฅผ ๊ด๋ฆฌ์์๊ฒ ๋ณด๋ด์ ์ผ๋ฐ ๊ณ์ ์ ๊ด๋ฆฌ์ ๊ณ์ ์ผ๋ก ๋ฐ๊พธ๋๋ก ํ๊ฑฐ๋, ๊ด๋ฆฌ์ ๊ณ์ ํจ์ค์๋๋ฅผ ๋ฐ๊พธ๋๋ฐ ์ด์ฉํ๋ค๋ฉด ํด๋น ์ฌ์ดํธ์ ๋ชจ๋ ์ ๋ณด๊ฐ ํดํน๋นํ๋ ๋ฐ๋ ์ค๋ ์๊ฐ์ด ๊ฑธ๋ฆฌ์ง ์๋๋ค.
<img src="http://auction.com/changeUserAcoount?id=admin&password=admin" width="0" height="0">
The XMLHttpRequest.withCredentials property is a Boolean that indicates whether or not cross-site Access-Control requests should be made using credentials such as cookies, authorization headers or TLS client certificates. Setting withCredentials has no effect on same-site requests.
ํค๋์ ์ ์ฉํ๋ ์ต์
์ผ๋ก ๋ณด์ ํต์ ์ค ๋ค๋ฅธ ๋๋ฉ์ธ(Cross) ์ฌ์ด์ ๋ณด์ ์ธ์ฆ(์ฟ ํค, TLS ์ธ์ฆ)์ ๋ณด๋ด๋ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ค.
์๋ฒ์ CORS ์ต์
์ค origin
๋๋ Access-Control-Allow-Origin
์ต์
์ด ์๋ฌด๋ ํ์ฉ *
์ผ ๊ฒฝ์ฐ ๋์ํ์ง ์๋๋ค.
๊ฐ์ ๋๋ฉ์ธ์ผ ๊ฒฝ์ฐ ์ ํํ์ง ์๋๋ค.
axios
.post(
'https://localhost:4000/users/login',
{
userId: this.state.username,
password: this.state.password,
},
{ 'Content-Type': 'application/json', withCredentials: true } // <------
)
The credentials read-only property of the Request interface indicates whether the user agent should send cookies from the other domain in the case of cross-origin requests. This is similar to XHRโs withCredentials flag, but with three available values (instead of two):
- omit: Never send cookies.
- same-origin: Send user credentials (cookies, basic http auth, etc..) if the URL is on the same origin as the calling script. This is the default value.
- include: Always send user credentials (cookies, basic http auth, etc..), even for cross-origin calls. (express์์ true์ ๋์ผ)
๋ณด์ ์ธ์ฆ ์ ๋ณด๋ฅผ(cookies, basic http auth, etc..) ๋ณด๋ด๋ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ค. ๊ธฐ๋ณธ๊ฐ์ same-origin์ผ ๊ฒฝ์ฐ ํ์ฉํ๋ค.
๊ต์ฐจ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ (Cross-Origin Resource Sharing, CORS) ์ต์
์ด๋ค. ํน์ origin์์ ์จ ํต์ ๋ง ํ์ฉํ๋ค. ์ฌ์ดํธ๋ฅผ ์ ํํด์ผ ํด๋ผ์ด์ธํธ์ withCredentials: true
์ต์
์ ํ์ฑํํ ์ ์๋ค. origin ์ ํ์ด ์์ด withCredentails
์ต์
์ด ์ ์ฉ๋๋ฉด ์๋ฌดํํ
๋ ๋์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ฟ๋ฆฌ๊ฒ ๋๋ค.
app.use(
cors({
origin: 'https://localhost:3000', // <----- client / withCredentials: true
methods: ['GET', 'POST', 'OPTIONS'],
credentials: true, // <------
})
);
๋ํ GET/POST ๋ฑ์ ๊ตฌ๋ถํ์ฌ ์ฃผ๋ ๊ฒ ์ญ์ ์ ์ฉํ๋ค. img ํ๊ทธ ๋ฑ์ ์ด์ฉํ ๊ฒฝ์ฐ GET ์์ฒญ์ผ๋ก ๋ค์ด์ค๊ฒ ๋ ๊ฒ์ด๊ณ , ๋ฐ๋ฉด ํํ ํ๋ฏ form์ ์ด์ฉํด ๊ฐ์ ๋ฐ์ ๊ฒฝ์ฐ POST๋ฅผ ์ด์ฉํ๊ฒ ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง๊ธฐ ๋๋ฌธ์ด๋ค.
์ฟ ํค ์ค์ ์ค sameSite
์ต์
์ด ์ ๊ธฐ๋ฅ์ ํฌํจํ๊ณ ์๋ค. ํฌ๋กฌ์ ๊ธฐ๋ณธ ๊ฐ์ Lax
๋ก GET์ ํฌํจํ ์ผ๋ถ ํ๊ทธ๋ง ์ฟ ํค ์ ์ก์ ํ์ฉํ๋ค.
// TODO: express-session ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํด ์ฟ ํค ์ค์ ์ ํด์ค ์ ์์ต๋๋ค.
app.use(
session({
secret: '@codestates', // ์ํธํ๋ก ์ฐ์ด๋ ํค
resave: false, // ์ธ์
์ ํญ์ ์ ์ฅํ ์ง ์ค์
saveUninitialized: true, // ์ธ์
์ด ์ ์ฅ๋๊ธฐ ์ uninitialized ์ํ๋ก ์ ์ฅ
cookie: { // ์ธ์
์ฟ ํค ์ค์
domain: 'localhost', // ์ฟ ํค๋ฅผ ๋ณด๋ด๋ ์ฌ์ดํธ(์๋ฒ)๋ฅผ ๋ช
์
path: '/', // URL ๊ฒฝ๋ก. ํ์ ๊ฒฝ๋ก๊น์ง ๋ชจ๋ ํฌํจ.
maxAge: 24 * 6 * 60 * 10000, // ์ฟ ํค ๋ง๋ฃ ์๊ฐ. ์ด๋จ์.
// XSRF(cross-site request forgery) ๊ณต๊ฒฉ ๋ฐฉ์ด ์ต์
sameSite: 'None', // ์ฌ์ดํธ ์ธ๋ถ์์ ์์ฒญ์ ๋ณด๋ผ ๋ ์ฟ ํค ์ ์ฑ
// Strict : ์ธ๋ถ ๋๋ฉ์ธ ์ฟ ํค ์ฐจ๋จ
// Lax : ์ธ๋ถ ๋๋ฉ์ธ ์ฟ ํค ์ผ๋ถ ํ์ฉ(HTTP get method / a href / link href)
// None : ์ธ๋ถ ๋๋ฉ์ธ ์ฟ ํค ๋ชจ๋ ํ์ฉ. ๊ฐ์ ๋ก origin ์ต์
๊ณผ ๊ฐ์ด ์จ์ผํจ
httpOnly: true, // ํด๋ผ์ด์ธํธ ์คํฌ๋ฆฝํธ๊ฐ ์ฟ ํค๋ฅผ ๋ชป๋ณด๊ฒ ํจ (document.cookie)
secure: true, // HTTPS ํ๊ฒฝ๋ง ์ฟ ํค๋ฅผ ์ ์ก
},
})
);
// CORS ์ฟ ํค ์ค์ ํ์ฑํ
app.use(
cors({
// origin: 'https://localhost:3000',
origin: true,
methods: ['GET', 'POST', 'OPTIONS'],
credentials: true, // ๋ค๋ฅธ ๋๋ฉ์ธ๊ฐ ์๊ฒฉ์ฆ๋ช
(credential, ์ฟ ํค ํฌํจ)์ ์ ์กํ ์ง ์ฌ๋ถ
})
);
๊ทธ ์ธ์๋ ํจ์ค์๋ ๋ณ๊ฒฝ ๊ฐ์ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ๋ค๋ฃฐ ๋์๋ ์ธ์ ์ ์์๋์(ํ ํฐ)๋ฅผ ๋ฐ๊ธํด์, ํด๋น ๋์๊ฐ ์๋ ์ํฉ์์ ํด๋น ๋์๋ค์ด ์ด๋ฃจ์ด์ง๋ฉด ์์ฒญ์ ๊ฑฐ๋ถํ๋ ๋ฐฉ๋ฒ์ ํตํ์ฌ ์ฌ์ฉ์๊ฐ ์ ๋ง๋ก ๋ณ๊ฒฝ์ ์๋ํ๋ ๊ฒฝ์ฐ์๋ง ๋ณ๊ฒฝ์ ์์ผ์ฃผ๋ ๋ฐฉ๋ฒ, ๋ณ๊ฒฝ ์์ CAPTCHA๋ฅผ ์ด์ฉํ์ฌ CAPTCHA ์ธ์ฆ์ฝ๋๊ฐ ์๊ฑฐ๋ ํ๋ฆฌ๋ฉด ๊ฑฐ๋ถํ๋๋ก ํ๋ ๋ฐฉ๋ฒ ๋ฑ์ด ์ด์ฉ๋๊ณ ์๋ค.
๋ฏผ๊ฐํ ์ ๋ณด๋ ์ถ๊ฐ ๋ณด์ ์กฐ์น๋ฅผ ํด๋๋๊ฒ ์ข๋ค. ์ฟ ํค๋ ๊ฐ์ข ๊ณต๊ฒฉ์ (CSRF) ํ๊ฒ์ด ๋๊ธฐ ์ฌ์ฐ๋ฏ๋ก ์ค์ํ ๋ณด์ ์ ๋ณด๋ ์ฟ ํค์ ํ์ฌํ๋ ๊ฒ์ ์ง์ํ๋ ๊ฒ์ ๊ถ๊ณ ํ๊ณ ์๋ค.
Cross-Site Scripting. CSS์ ๊ตฌ๋ถํ๊ธฐ ์ํด์ ์ ๋จ์ด๋ฅผ X
๋ก ์ด๋ค.
ํด์ปค๊ฐ ์๋๋์ง ์๋ ๋ฐฉ์์ผ๋ก ์
์์ ์ธ ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ด๋๋ ๊ณต๊ฒฉ์ด๋ค. ์ทจ์ฝํ ์ฌ์ดํธ์ ๊ฒฝ์ฐ, ํด์ปค๊ฐ ๋๊ธ์ <script>
์๋ฆฌ๋จผํธ ํํ์ ๋ฌธ์์ด์ ๋ฑ๋กํ๋ฉด ๋ฌธ์์ด๋ก ๋ณด์ฌ์ง์ง ์๊ณ ํ๊ทธ๋ก ์ธ์๋๊ณ ๋ค๋ฅธ ์ฌ์ฉ์๋ ์
์์ ์ธ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๊ฒ ๋๋ค.
SQL injection๊ณผ ํจ๊ป ์น ์์์ ๊ฐ์ฅ ๊ธฐ์ด์ ์ธ ์ทจ์ฝ์ ๊ณต๊ฒฉ ๋ฐฉ๋ฒ์ ์ผ์ข ์ผ๋ก, ์ ์์ ์ธ ์ฌ์ฉ์๊ฐ ๊ณต๊ฒฉํ๋ ค๋ ์ฌ์ดํธ์ ์คํฌ๋ฆฝํธ๋ฅผ ๋ฃ๋ ๊ธฐ๋ฒ์ ๋งํ๋ค. ๊ณต๊ฒฉ์ ์ฑ๊ณตํ๋ฉด ์ฌ์ดํธ์ ์ ์ํ ์ฌ์ฉ์๋ ์ฝ์ ๋ ์ฝ๋๋ฅผ ์คํํ๊ฒ ๋๋ฉฐ, ๋ณดํต ์๋์น ์์ ํ๋์ ์ํ์ํค๊ฑฐ๋ ์ฟ ํค๋ ์ธ์ ํ ํฐ ๋ฑ์ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ํ์ทจํ๋ค.
๊ณต๊ฒฉ ๋ฐฉ๋ฒ์ ๋ฐ๋ผ Stored XSS์ Reflected XSS๋ก ๋๋๋ค. Stored XSS๋ ์ฌ์ดํธ ๊ฒ์ํ์ด๋ ๋๊ธ, ๋๋ค์ ๋ฑ ์คํฌ๋ฆฝํธ๊ฐ ์๋ฒ์ ์ ์ฅ๋์ด ์คํ๋๋ ๋ฐฉ์์ด๊ณ , Reflected XSS๋ ๋ณดํต URL ํ๋ผ๋ฏธํฐ(ํนํ GET ๋ฐฉ์)์ ์คํฌ๋ฆฝํธ๋ฅผ ๋ฃ์ด ์๋ฒ์ ์ ์ฅํ์ง ์๊ณ ๊ทธ ์ฆ์ ์คํฌ๋ฆฝํธ๋ฅผ ๋ง๋๋ ๋ฐฉ์์ด๋ค. ํ์ ๋ ๋ด์ฉ ๋๋ถ๋ถ์ Stored XSS๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค. Reflected XSS์ ๊ฒฝ์ฐ ๋ธ๋ผ์ฐ์ ์์ฒด์์ ์ฐจ๋จํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ ์๋์ ์ผ๋ก ๊ณต๊ฒฉ์ ์ฑ๊ณต์ํค๊ธฐ ์ด๋ ต๋ค.
ํฌ๋ก์ค ์ฌ์ดํธ ์คํฌ๋ฆฝํ ์ด๋ ์ด๋ฆ ๋ต๊ฒ, ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉํ์ฌ ๊ณต๊ฒฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. ๊ณต๊ฒฉ ๋ฐฉ๋ฒ์ด ๋จ์ํ๊ณ ๊ฐ์ฅ ๊ธฐ์ด์ ์ด์ง๋ง, ๋ง์ ์น์ฌ์ดํธ๋ค์ด XSS์ ๋ํ ๋ฐฉ์ด ์กฐ์น๋ฅผ ํด๋์ง ์์ ๊ณต๊ฒฉ์ ๋ฐ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. ์ฌ๋ฌ ์ฌ์ฉ์๊ฐ ์ ๊ทผ ๊ฐ๋ฅํ ๊ฒ์ํ ๋ฑ์ ์ฝ๋๋ฅผ ์ฝ์ ํ๋ ๊ฒฝ์ฐ๋ ๋ง์ผ๋ฉฐ, ๊ฒฝ์ฐ์ ๋ฐ๋ผ์๋ ๋ฉ์ผ๊ณผ ๊ฐ์ ๋งค์ฒด๋ฅผ ํตํด์๋ ์ ํ๋๋ค. ์ฌ์ง์ด๋ ๋๋ค์์ ์ฝ๋๋ฅผ ์ฌ๊ธฐ๋ ํ๋ค.
์คํฌ๋ฆฝํธ ํ๊ทธ
์คํฌ๋ฆฝํธ ํ๊ทธ์ ์คํฌ๋ฆฝํธ๋ฅผ ์คํ์ํจ๋ค. ์ํ๊น๊ฒ๋, ๋งค์ฐ ์ ์งํ ๋ฐฉ๋ฒ์ด๋ผ ๋๋ถ๋ถ์ ์ฌ์ดํธ์์ ๋ง๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
<script>alert('XSS');</script>
์๋ฐ์คํฌ๋ฆฝํธ ๋งํฌ
๋งํฌ ํ๊ทธ๋ก ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๋ค.
<a href="javascript:alert('XSS')">XSS</a>
์ด๋ฒคํธ ์์ฑ
์ด๋ฒคํธ ์์ฑ์ ์ฌ์ฉํ๋ค. ์ด๋ฒคํธ ์์ฑ์ผ๋ก ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ ์ ์๋ค. ์ฃผ๋ก on ์ผ๋ก ์์ํ๋ ์์ฑ์ด ์ด๋ฒคํธ ์์ฑ์ด๋ค. ์์ฃผ ์ฌ์ฉ๋๋ ์ด๋ฒคํธ ์์ฑ์ผ๋ก๋ onload onerror onclick ๋ฑ์ด ์๋ค.
<img src="#">
๋ธ๋๋ฆฌ์คํธ ์ฐํ
์๋ ค์ง์ง ์์ ํ๊ทธ์ ์์ฑ๋ค์ ์ฌ์ฉํ๋ค. ๋ธ๋ ๋ฆฌ์คํธ ๋ฐฉ์์ผ๋ก ๋ง๋ ์ฌ์ดํธ์ ์ฌ์ฉํ ์ ์๋ค.
<ruby>XSS</ruby>
๋ด์ฉ ๋๋
ํ
๋ฐ์ดํ๋ก ๊ฐ์ธ๋ ๋ฌธ์์ด ์ฌ์ด์ ๊ณต๋ฐฑ ๋ฌธ์๋ค์ ๋ฃ๊ณ , HTML ์ธ์ฝ๋๋ฅผ ํ์ฌ ๋๋
ํํ๋ค. ์ผ๋ถ ๋ธ๋ผ์ฐ์ ์์ javascript:
๋งํฌ ์ฌ์ด์ ๊ณต๋ฐฑ ๋ฌธ์๊ฐ ๋ค์ด๊ฐ ์ ์๊ณ , HTML ์ธ์ฝ๋๋ฅผ ํด๋ ๋์ฝ๋๋ ๋ด์ฉ์ด ์ถ๋ ฅ๋๋ค๋ ์ ์ ์ด์ฉํ๋ค. ์ฌ๊ธฐ์์๋ '์๋ฐ์คํฌ๋ฆฝํธ ๋งํฌ' ๋ฐฉ๋ฒ๊ณผ ์ฌ์ฉํ์์ง๋ง, ๋น์ฐํ ๋ค๋ฅธ ๋ฐฉ๋ฒ๊ณผ ํจ๊ป ์ฌ์ฉํ ์ ์๋ค.
<a href="javas
cript
:
alert
('XSS')">XSS</a>
https://namu.wiki/w/CSRF
https://www.imperva.com/learn/application-security/csrf-cross-site-request-forgery/
https://sj602.github.io/2018/07/14/what-is-CSRF/
https://docs.w3cub.com/dom/request/credentials
https://docs.w3cub.com/dom/xmlhttprequest/withcredentials
https://namu.wiki/w/XSS