Post

[Security] Secure Coding(6-2) - SQL Injection

[Security] Secure Coding(6-2) - SQL Injection

๐Ÿ”’ ์‹œํ์–ด ์ฝ”๋”ฉ ์ˆ˜์—… ์ •๋ฆฌ

SQL Injection

๐Ÿ“šSQL Injection: ์›น์•ฑ์—์„œ ์ž…๋ ฅ ๋ฐ›์•„ DB๋กœ ์ „๋‹ฌํ•˜๋Š” ์ •์ƒ์ ์ธ ์ฟผ๋ฆฌ(CRUD)๋ฅผ ๋ณ€์กฐ, ์‚ฝ์ž…ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ถˆ๋ฒ•์ ์ธ ๋ฐ์ดํ„ฐ ์—ด๋žŒ, ์‹œ์Šคํ…œ ๋ช…๋ น ์ˆ˜ํ–‰ ๋“ฑ ๋น„์ •์ƒ์ ์ธ ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•˜๋Š” ๊ธฐ๋ฒ•

  • ๋ชจ๋“  ์ข…๋ฅ˜์˜ DBMS(Database Management System)์— ์ ์šฉ ๊ฐ€๋Šฅํ•œ ๊ณต๊ฒฉ ๊ธฐ๋ฒ•์ด๋ฉฐ, ์ง€์†์ ์œผ๋กœ ๋ฐœ์ „๋˜๊ณ  ์žˆ์Œ

โŒํ”ผํ•ด๋‚ด์šฉ:

  • ์•…์„ฑ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰, ์™ธ๋ถ€ ํ”„๋กœ๊ทธ๋žจ ์‚ฌ์šฉ๊ฐ€๋Šฅ, DB ์ •๋ณด ์—ด๋žŒ,์ถ”๊ฐ€,์ˆ˜์ •,์‚ญ์ œ ๊ฐ€๋Šฅ
  • ํ”„๋กœ์‹œ์ €๋ฅผ ํ†ตํ•œ ์šด์˜์ฒด์ œ ๋ช…๋ น์–ด ์ˆ˜ํ–‰, ๋ถˆ๋ฒ• ๋กœ๊ทธ์ธ ๋“ฑ์˜ ์นจํ•ด ์‚ฌ๊ณ ๊ฐ€ ๋ฐœ์ƒ ๊ฐ€๋Šฅ

alt text

SQL Injection ๋ฐœ์ƒ ์›์ธ

  1. ๋™์  ์ฟผ๋ฆฌ์—์„œ ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๊ฒ€์ฆ ๋ถ€์กฑ
    • ํผ, URL ํŒŒ๋ผ๋ฏธํ„ฐ, API ์š”์ฒญ ๋“ฑ ๋ชจ๋“  ์™ธ๋ถ€ ์ž…๋ ฅ์— ๋Œ€ํ•ด ๊ฒ€์ฆ ๋กœ์ง์ด ์—†๋Š” ๊ฒฝ์šฐ ๋ฐœ์ƒ
    • ์ž…๋ ฅ ๊ฐ’ ๋‚ด ํŠน์ˆ˜๋ฌธ์ž(' , ", ; ๋“ฑ)๋ฅผ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์•„ SQL ๊ตฌ๋ฌธ์ด ์˜๋„์น˜ ์•Š๊ฒŒ ๋ณ€๊ฒฝ๋จ
1
2
-- userInput์— admin' OR '1'='1 ์„ ์‚ฝ์ž…ํ•˜๋ฉด ํ•ญ์ƒ ์ฐธ์ด ๋˜์–ด ์ธ์ฆ ์šฐํšŒ๋จ
SELECT * FROM users WHERE username = '" + userInput + "';
  1. ์—๋Ÿฌ ๋ฉ”์„ธ์ง€ ๋ฐ ์ •๋ณด ๋…ธ์ถœ
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋‚ด๋ถ€ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๋ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ •๋ณด๋ฅผ ๋…ธ์ถœํ•  ๊ฒฝ์šฐ, ๊ณต๊ฒฉ์ž๊ฐ€ ์ด๋ฅผ ๋ถ„์„ํ•˜์—ฌ ์ถ”๊ฐ€ ๊ณต๊ฒฉ์„ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ์Œ.
    • ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์— ํฌํ•จ๋œ ๋‚ด์šฉ์œผ๋กœ ๋‹ค์–‘ํ•œ ์ •๋ณด ํš๋“ ๊ฐ€๋Šฅ
      • โ†’ SQL ๊ตฌ๋ฌธ ์˜ค๋ฅ˜, ํ…Œ์ด๋ธ”๋ช…, ์ปฌ๋Ÿผ ๋ช…, DB ๋ฒ„์ „ ๋“ฑ
1
2
-- userInput์— 1' AND db_name() = 0-- ์‚ฝ์ž…ํ•˜๋ฉด ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ด๋ฆ„์ด ๋“œ๋Ÿฌ๋‚จ.
SELECT * FROM users WHERE usernamne = '" + userInput + "';
  1. ๊ถŒํ•œ ๋ฐ ๋ณด์•ˆ ์„ค์ • ๋ฏธํก
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉํ•˜๋Š” DB ๊ณ„์ •์ด ๋ถˆํ•„์š”ํ•œ ๊ถŒํ•œ(์˜ˆ: DROP, ALTER, EXECUTE ๋“ฑ)์„ ๊ฐ€์ง€๊ณ  ์žˆ์„ ๊ฒฝ์šฐ, ๊ณต๊ฒฉ์ž๊ฐ€ ์ด๋ฅผ ์•…์šฉ ๊ฐ€๋Šฅ
    • ์ทจ์•ฝํ•œ ์ €์žฅ ํ”„๋กœ์‹œ์ €๋ฅผ ํ†ตํ•ด ์‹œ์Šคํ…œ ๋ช…๋ น์–ด ์‹คํ–‰์ด๋‚˜ ์ถ”๊ฐ€ ๊ถŒํ•œ ์ƒ์Šน ๊ณต๊ฒฉ ๊ฐ€๋Šฅ

SQL Injection ๊ณต๊ฒฉ ๋ฐฉ์‹

1
2
3
4
<form action="login.jsp">
    ID : <input type="text" name="id" />
    Password : <input type="password" name="password" />
</form>

์œ„์˜ html์—์„œ์˜ id์™€ password ๊ฐ’์ด ์„œ๋ฒ„๋กœ ๋ณด๋‚ด์ง€๋ฉด ๋‹ค์Œ java์˜ sql์— ๋ฌธ์ž์—ด๋กœ ์ €์žฅ๋œ๋‹ค.

1
2
String sql = "select * from member where id = '" + request.getParameter("id") + "' " + "and password = '" + request.getParameter("password") + "' ";
statement.executeQuery(sql);

์ž…๋ ฅ ์˜ˆ์‹œ

  • ์ •์ƒ ์š”์ฒญ: login.jsp?id=gildong&password=gdhong
  • ๊ณต๊ฒฉ ์ฝ”๋“œ: login.jsp?id=a' or 'a'='a&password=a' or 'a'='a
    login.jsp?id=admin' -- &password=x
    login.jsp?id=admin' # &password=x

Error-based SQL Injection

๐Ÿ“šError-based SQL Injection: ์ถ”๊ฐ€ ๊ณต๊ฒฉ์— ํ•„์š”ํ•œ ์ •๋ณด๊ฐ€ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€์— ํฌํ•จ๋˜์–ด ๋…ธ์ถœ๋  ์ˆ˜ ์žˆ๋„๋ก ์ž…๋ ฅ ๊ฐ’์„ ์กฐ์ž‘

๐Ÿ’ก๊ณต๊ฒฉ ์˜ˆ์‹œ:

  • ': 1๊ฐœ์˜ ๋”ฐ์˜ดํ‘œ โ†’ ์ธ์ ์…˜ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ๋ฐ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ ํ™•์ธ์ด ๊ฐ€๋Šฅ
  • ' having 1 = 1 -- โ†’ ์˜ค๋ฅ˜์—์„œ ํ…Œ์ด๋ธ”๋ช…, ์ปฌ๋Ÿผ ๋ช… ํ™•์ธ์ด ๊ฐ€๋Šฅ
  • ' and db_name() = 1 -- โ†’ ํ˜„์žฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์ด๋ฆ„ ํ™•์ธ์ด ๊ฐ€๋Šฅ
    • ์˜ค๋ฅ˜๋ฉ”์„ธ์ง€: Conversion failed when converting the nvarchar value 'database_xmoonanx' to data type int.
  • ' and 1 = (select @@version) -- โ†’ ํ˜„์žฌ ์„ค์น˜๋œ SQL Server์˜ ์‹œ์Šคํ…œ ๋ฐ ๋นŒ๋“œ ์ •๋ณด ํ™•์ธ์ด ๊ฐ€๋Šฅ

Union-based SQL Injection

๐Ÿ“šUnion-based SQL Injection: UNION ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์›๋ž˜ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์™€ ๊ณต๊ฒฉ์ž๊ฐ€ ์›ํ•˜๋Š” ์ •๋ณด๋ฅผ ํ•จ๊ป˜ ์ถœ๋ ฅ์‹œํ‚ค๋Š” ๊ธฐ๋ฒ•

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- ์ฒซ๋ฒˆ์งธ ์ž‘์€ ๋”ฐ์˜ดํ‘œ๋Š” ๋ฌด์‹œ
-- 1๋‹จ๊ณ„: ์ปฌ๋Ÿผ ์ˆ˜ ํ™•์ธ 
'   admin' union select 1,2,3,4,5,6 #

-- 2๋‹จ๊ณ„: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฒ„์ „ ํ™•์ธ 
'   admin' union select version(),2,3,4,5,6

-- 3๋‹จ๊ณ„: ์Šคํ‚ค๋งˆ ์ •๋ณด ์ถ”์ถœ 
'   admin' union select schema_name,2,3,4,5,6 from information_schema.schemata #

-- 4๋‹จ๊ณ„: ํ…Œ์ด๋ธ” ๋ชฉ๋ก ํ™•์ธ
'   admin' union select group_concat(table_name),2,3,4,5,6 from information_schema.tables where table_schema=database() #

-- 5๋‹จ๊ณ„: ์‚ฌ์šฉ์ž ๊ณ„์ • ์ •๋ณด ํƒˆ์ทจ 
'   admin' union select idx,userid,userpw,username,5,6 from board_member #

alt text

  • ๊ณต๊ฒฉ์ž๋“ค์ด DB ๋‚ด๋ถ€๊ตฌ์กฐ๋ฅผ ํ™œ์šฉํ•ด์„œ ๊ณต๊ฒฉํ•˜๋ ค๋ฉด DBMS ์‹œ์Šคํ…œ ํ…Œ์ด๋ธ”์˜ ๊ตฌ์กฐ๋ฅผ ํŒŒ์•…ํ•ด์•ผํ•œ๋‹ค.
  • DB๊ฐ€ ๋™์ž‘ํ•˜๋Š” ๋™์•ˆ ์‚ฌ์šฉํ•˜๋Š” ํ…Œ์ด๋ธ”์ด๋ฏ€๋กœ ๋ง‰๋Š” ๊ฒƒ๋„ ์–ด๋ ต๋‹ค.
  • ๋งŽ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋“ค์ด ์ด๋Ÿฐ ์‹œ์Šคํ…œ ํ…Œ์ด๋ธ”์„ ํ™œ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๋„ ๋งŽ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฐ ์ƒํ™ฉ์ด ์•„๋‹ˆ๋ผ๋ฉด ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž๋“ค์˜ ๊ถŒํ•œ์„ ๋ง‰๋Š”๊ฒŒ ์ข‹๋‹ค.

Blind SQL Injection

๐Ÿ“šBlind SQL Injection: ์ง์ ‘์ ์ธ ๊ฒฐ๊ณผ ์ถœ๋ ฅ ์—†์ด ์„œ๋ฒ„์˜ ๋ฐ˜์‘ ์ฐจ์ด๋ฅผ ์ด์šฉํ•˜์—ฌ ์ •๋ณด๋ฅผ ํ•œ ๊ธ€์ž์”ฉ ์ถ”์ถœํ•˜๋Š” ๊ธฐ๋ฒ•

  • ์ฐธ์ธ ์กฐ๊ฑด(์ฟผ๋ฆฌ ์‹คํ–‰ ๊ฒฐ๊ณผ๊ฐ€ ์กด์žฌ)๊ณผ ๊ฑฐ์ง“์ธ ์กฐ๊ฑด(์ฟผ๋ฆฌ ์‹คํ–‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Œ)์„ ๋ฒˆ๊ฐˆ์•„ ๊ฐ€๋ฉด์„œ ์ž…๋ ฅ๋˜๋„๋ก ์ž…๋ ฅ ๊ฐ’์„ ์กฐ์ž‘

Content-based ๋ฐฉ์‹๊ณผ Time-based ๋ฐฉ์‹์ด ์žˆ์Œ

  • Content-based: ์กฐ๊ฑด๋ฌธ์˜ ์ฐธ/๊ฑฐ์ง“์— ๋”ฐ๋ฅธ ํŽ˜์ด์ง€ ๋‚ด์šฉ ์ฐจ์ด๋ฅผ ์ด์šฉ
1
2
3
4
5
6
-- ์ฒซ๋ฒˆ์งธ ์ž‘์€ ๋”ฐ์˜ดํ‘œ๋Š” ๋ฌด์‹œ
-- a~z๊นŒ์ง€ ์ฒซ ๋ฒˆ์งธ ๊ธ€์ž๊ฐ€ 'a'์ธ์ง€ ํ™•์ธ 
'   Data' and substring(user_name(1),1,1)='a' --

-- ๋‘ ๋ฒˆ์งธ ๊ธ€์ž ํ™•์ธ
'   Data' and substring(user_name(1),2,1)='d' --
  • Time-based: ์กฐ๊ฑด๋ฌธ์— ๋”ฐ๋ฅธ ์‘๋‹ต ์‹œ๊ฐ„ ์ฐจ์ด๋ฅผ ์ด์šฉ
1
2
3
-- ์กฐ๊ฑด์ด ์ฐธ์ด๋ฉด 5์ดˆ ์ง€์—ฐ
'   Data' and substring(user_name(1),1,1)='a' waitfor delay '0:0:5'
-- ์ง€์†์ ์œผ๋กœ ๋ฐ˜๋ณต. ๊ณต๊ฒฉ์‹œ๊ฐ„์ด ๋งŽ์ด ๊ฑธ๋ฆฌ๋ฏ€๋กœ ํˆด์— ์˜ํ•ด ์ž๋™ํ™” ๊ณต๊ฒฉ์„ ์‹œ๋„ํ•œ๋‹ค.

SQL Injection ์ง„๋‹จ ๋ฐฉ๋ฒ•

์ฝ”๋“œ ๋ฆฌ๋ทฐ

๐Ÿ“š์ฝ”๋“œ ๋ฆฌ๋ทฐ: ๊ฐœ๋ฐœ์ž๊ฐ€ ์ž‘์„ฑํ•œ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์ฒด๊ณ„์ ์œผ๋กœ ๊ฒ€ํ† ํ•˜์—ฌ ๋ฒ„๊ทธ ๋ฐ ๋ณด์•ˆ ์ทจ์•ฝ์ ์„ ์ฐพ์•„๋‚ด๋Š” ํ”„๋กœ์„ธ์Šค

  • SQL ์ธ์ ์…˜๊ณผ ๊ฐ™์€ ์ทจ์•ฝ์ ์€ ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ํ†ตํ•ด ์กฐ๊ธฐ์— ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์Œ

๐Ÿ’กSQL ์ธ์ ์…˜ ์ทจ์•ฝ์ ์˜ ์ฃผ์š” ์›์ธ

  • ๋™์  SQL ์‚ฌ์šฉ: ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๊ฐ’์„ ๋ฌธ์ž์—ด๋กœ ์ง์ ‘ ์ฟผ๋ฆฌ์— ์‚ฝ์ž…
  • ์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ ๋ฏธํก: ์‚ฌ์šฉ์ž ์ž…๋ ฅ์— ๋Œ€ํ•œ ํ•„ํ„ฐ๋ง ๋˜๋Š” ์ด์Šค์ผ€์ดํ•‘์ด ๋ถ€์กฑ
  • ์ž˜๋ชป๋œ ๊ถŒํ•œ ์„ค์ •: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ ๊ถŒํ•œ์ด ๊ณผ๋„ํ•˜๊ฒŒ ๋ถ€์—ฌ๋จ

โœ…์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ค‘์š”์„ฑ:

  • ๋ณด์•ˆ ์ทจ์•ฝ์ ์ด ์ œํ’ˆ ์ถœ์‹œ ์ „ ๋‹จ๊ณ„์—์„œ ์‚ฌ์ „์— ์ œ๊ฑฐ๋จ
  • ํŒ€ ๋‚ด ๋ณด์•ˆ ์ธ์‹ ์ œ๊ณ  ๋ฐ ๋ชจ๋ฒ” ์‚ฌ๋ก€ ๊ณต์œ 
  • ์ž๋™ํ™” ๋„๊ตฌ์™€ ์ˆ˜๋™ ๋ฆฌ๋ทฐ๋ฅผ ๋ณ‘ํ–‰ํ•˜์—ฌ ์ทจ์•ฝ์  ํƒ์ง€์œจ ํ–ฅ์ƒ

์ฝ”๋“œ ๋ฆฌ๋ทฐ ๋ฐฉ๋ฒ•

  1. ๋™์  SQL ๊ตฌ๋ฌธ ์‚ฌ์šฉ ์—ฌ๋ถ€ ์ ๊ฒ€
    • ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๊ฐ’์„ ์ง์ ‘ ์ฟผ๋ฆฌ ๋ฌธ์ž์—ด์— ๊ฒฐํ•ฉํ•˜๋Š” ๊ฒฝ์šฐ
1
2
3
4
5
6
7
8
9
public User findUserByUsername(String username) {
    String sql = "SELECT * FROM users WHERE username = '" + username + "'";
    try {
        return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class));
    } 
    catch (EmptyResultDataAccessException e) {
        return null;
    }
} 
  1. ์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ ๋ฐ ์ด์Šค์ผ€์ดํ•‘ ์ ๊ฒ€
    • ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๊ฐ’์— ๋Œ€ํ•ด ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ ๊ธฐ๋ฐ˜์˜ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์ˆ˜ํ–‰
    • ํŠน์ˆ˜๋ฌธ์ž๋‚˜ ์œ„ํ—˜ ๋ฌธ์ž์— ๋Œ€ํ•œ ์ด์Šค์ผ€์ดํ•‘ ์ ์šฉ ์—ฌ๋ถ€ ํ™•์ธ

์ž๋™ํ™”๋œ ์ง„๋‹จ ๋„๊ตฌ ํ™œ์šฉ

์ •์  ๋ถ„์„ ๋„๊ตฌ

  • ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ๋ณด์•ˆ ์ทจ์•ฝ์ (์˜ˆ: ๋™์  SQL, ์ž…๋ ฅ๊ฐ’ ์ฒ˜๋ฆฌ ๋ฏธํก ๋“ฑ)์„ ์ž๋™์œผ๋กœ ํƒ์ง€
    • ์˜ˆ: SonarQube, Checkmarx, Fortify

๋™์  ๋ถ„์„ ๋„๊ตฌ

  • ์‹คํ–‰ ์ค‘์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•ด ์‹ค์ œ ๊ณต๊ฒฉ ์‹œ๋‚˜๋ฆฌ์˜ค(์˜ˆ: OWASP ZAP)๋ฅผ ์ ์šฉํ•ด ์ทจ์•ฝ์  ๊ฒ€์ฆ
    • ์˜ˆ: OWASP ZAP
  • ์ž๋™ํ™” ๋„๊ตฌ์˜ ํ•„์š”์„ฑ
    • ๊ด‘๋ฒ”์œ„ํ•œ ์ฝ”๋“œ ๊ฒ€์ฆ: ์ˆ˜๋™ ๋ฆฌ๋ทฐ๋งŒ์œผ๋กœ๋Š” ๋†“์น˜๊ธฐ ์‰ฌ์šด ์ทจ์•ฝ์ ์„ ์ฒด๊ณ„์ ์œผ๋กœ ํƒ์ง€
    • ๋ฐ˜๋ณต์  ๊ฒ€์‚ฌ: CI/CD ํŒŒ์ดํ”„๋ผ์ธ์— ํ†ตํ•ฉํ•˜์—ฌ ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‹œ๋งˆ๋‹ค ์ž๋™ ์ ๊ฒ€
  • SQL ์ธ์ ์…˜ ์ทจ์•ฝ์  ํƒ์ง€ ์—ญํ• 
    • ์‚ฌ์šฉ์ž ์ž…๋ ฅ๊ณผ ๋™์  SQL ๊ตฌ๋ฌธ ์กฐํ•ฉ์„ ์ž๋™์œผ๋กœ ์Šค์บ”ํ•˜์—ฌ ์œ„ํ—˜ ํŒจํ„ด์„ ์‹๋ณ„
    • ์ทจ์•ฝํ•œ ์ฝ”๋“œ ํŒจํ„ด(์˜ˆ: ๋ฌธ์ž์—ด ๊ฒฐํ•ฉ์œผ๋กœ SQL ์ƒ์„ฑ)์„ ๋ณด๊ณ ์„œ๋กœ ์ œ๊ณต

์‹œํ์–ด ์ฝ”๋”ฉ ์ ์šฉ

alt text

alt text

ํ”„๋ ˆ์ž„์›Œํฌ๋ณ„ SQL Injection ๋ฐฉ์–ด ๋ฐฉ๋ฒ•

JDBC Statement ๋ฐฉ์‹

  • PreparedStatement ์˜ setXXX()๋ฉ”์†Œ๋“œ๋กœ ์™ธ๋ถ€ ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ์ฟผ๋ฆฌ๋ฌธ์— ์„ค์ •ํ•˜์—ฌ ์ฟผ๋ฆฌ ๊ตฌ์กฐ๊ฐ€ ๋ณ€์กฐ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€
    • ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ์œผ๋กœ SQL๊ณผ ๋ฐ์ดํ„ฐ ๋ถ„๋ฆฌ
1
2
3
4
5
6
// โŒ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ์ฝ”๋“œ
String query = "SELECT * FROM users WHERE userid ='"+ userid + "'" +
"AND password='" + password + "'";

Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query);
1
2
3
4
5
6
// โœ…์•ˆ์ „ํ•œ ์ฝ”๋“œ
String query = "SELECT * FROM users WHERE userid=? AND password=?";
PreparedStatement stmt = connection.prepareStatement(query);
stmt.setString(1, userid);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();

Hibernate Query Language (HQL)

  • ๊ฐ์ฒด ์ง€ํ–ฅ ์ฟผ๋ฆฌ ์–ธ์–ด๋กœ SQL ์ง์ ‘ ์กฐ์ž‘ ๋ฐฉ์ง€
  • ์œ„์น˜ ๊ธฐ๋ฐ˜(?0) ๋ฐ ์ด๋ฆ„ ๊ธฐ๋ฐ˜(:param) ํŒŒ๋ผ๋ฏธํ„ฐ
1
2
3
// โŒ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ์ฝ”๋“œ
List results = session.createQuery(
"from Orders as orders where orders.id = " + currentOrder.getId()).list();
1
2
3
// โœ…์•ˆ์ „ํ•œ ์ฝ”๋“œ
Query hqlQuery = session.createQuery("from Orders as orders where orders.id = ?0");
List results = hqlQuery.setString(0, "123-456-7890").list();

MyBatis ๋ฐฉ์‹

  • #{} ๊ตฌ๋ฌธ์œผ๋กœ ์ž๋™ PreparedStatement ์ƒ์„ฑ
  • ๋™์  SQL์—์„œ๋„ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ ์œ ์ง€
1
2
3
4
<!-- โŒ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ์ฝ”๋“œ -->
<select id="getPerson" parameterType="String" resultType="org.application.vo.Person">
    SELECT * FROM PERSON WHERE NAME LIKE '${name}'
</select>
1
2
3
4
<!-- โœ…์•ˆ์ „ํ•œ ์ฝ”๋“œ -->
<select id="getPerson" parameterType="String" resultType="org.application.vo.Person">
    SELECT * FROM PERSON WHERE NAME = #{name}
</select>

Java Persistence API(JPA) ๋ฐฉ์‹

  • JPQL(Java Persistence Query Language) ์‚ฌ์šฉ
  • ์—”ํ‹ฐํ‹ฐ ๊ธฐ๋ฐ˜ ์ฟผ๋ฆฌ๋กœ SQL ์ง์ ‘ ์กฐ์ž‘ ๋ฐฉ์ง€
1
2
// โŒ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ์ฝ”๋“œ
List results = entityManager.createQuery("Select order from Orders order where order.id = " + orderId).getResultList();
1
2
3
// โœ…์•ˆ์ „ํ•œ ์ฝ”๋“œ
Query jpqlQuery = entityManager.createQuery("Select order from Orders order where order.id= ?1");
List results = jpqlQuery.setParameter(1, "123-456-7890").getResultList();

์ž…๋ ฅ ๊ฐ’ ํ•„ํ„ฐ๋ง

alt text

ํŠน์ˆ˜ ๋ฌธ์ž ์ธ์ฝ”๋”ฉ์„ ํ†ตํ•ด ์œ„ํ—˜ ๋ฌธ์ž๋ฅผ ์•ˆ์ „ํ•œ ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜


์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ

  • SQLMap์„ ํ™œ์šฉํ•œ ๊ฒ€์ฆ ํ…Œ์ŠคํŠธ ๐Ÿ“šSQLMap: SQL ์ธ์ ์…˜ ์ทจ์•ฝ์ ์„ ์ž๋™์œผ๋กœ ํƒ์ง€ํ•˜๊ณ  ์•…์šฉํ•˜๋Š” ์˜คํ”ˆ์†Œ์Šค ์นจํˆฌ ํ…Œ์ŠคํŠธ ๋„๊ตฌ

  • SQLMap์„ ํ™œ์šฉํ•˜์—ฌ, ๊ตฌํ˜„๋œ ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ์ด ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธ

๊ฒ€์ฆ ํ…Œ์ŠคํŠธ ์˜ˆ์‹œ

1
sqlmap -u "http://example.com/search?keyword=test" --risk=3 --level=5 --batch
  • -u: ํƒ€๊ฒŸ URL ์ง€์ • (GET ํŒŒ๋ผ๋ฏธํ„ฐ ํฌํ•จ)
  • --batch: ๋ชจ๋“  ์งˆ๋ฌธ์— ์ž๋™์œผ๋กœ ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ
  • --risk [1-3]: ์œ„ํ—˜ ์ˆ˜์ค€ ์„ค์ • (๋†’์„์ˆ˜๋ก ๋‹ค์–‘ํ•œ ํŽ˜์ด๋กœ๋“œ ์‹œ๋„)
  • --level [1-5]: ํ…Œ์ŠคํŠธ ๊ฐ•๋„ ์กฐ์ • (๋†’์„์ˆ˜๋ก ๊ฒ€์‚ฌํ•˜๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ํŽ˜์ด๋กœ๋“œ๊ฐ€ ๋งŽ์•„์ง)
  • --dump: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋‚ด์šฉ์„ ๋คํ”„ (ํ…Œ์ŠคํŠธ ๋ชฉ์  ์‹œ์—๋งŒ ์‚ฌ์šฉ, ์šด์˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์ฃผ์˜ํ•ด์•ผ ํ•จ)

โœ…ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ํŒ๋‹จ:

  • "Parameter: keyword (GET) appears to be 'safe'" ์ถœ๋ ฅ ์‹œ ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐฉ์–ด๋˜์—ˆ์Œ
  • SQL์ธ์ ์…˜ ๊ด€๋ จ ์ทจ์•ฝ์  ๊ฒฝ๊ณ ๊ฐ€ ์ถœ๋ ฅ๋˜๋ฉด, ์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ ๋กœ์ง์„ ๋ณด์™„ํ•ด์•ผ ํ•จ

๋ณด์•ˆ ๋กœ๊ทธ ๊ธฐ๋ก ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง

ํ•„์š”์„ฑ

  • ๐Ÿšจ์กฐ๊ธฐ ํƒ์ง€
    • SQL ์ธ์ ์…˜ ๊ณต๊ฒฉ์„ ์ค‘์‹ฌ์œผ๋กœ ๋น„์ •์ƒ์ ์ธ ์ฟผ๋ฆฌ ํŒจํ„ด์ด๋‚˜ ์˜ค๋ฅ˜ ๋กœ๊ทธ๋ฅผ ํƒ์ง€ํ•˜์—ฌ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ณต๊ฒฉ ์ง•ํ›„๋ฅผ ๋น ๋ฅด๊ฒŒ ํŒŒ์•…
  • ๐Ÿ”์‚ฌํ›„ ๋ถ„์„
    • ๊ณต๊ฒฉ ๋ฐœ์ƒ ์‹œ ๋กœ๊ทธ๋ฅผ ํ†ตํ•ด ์›์ธ ๋ถ„์„ ๋ฐ ๋Œ€์‘ ์ „๋žต ์ˆ˜๋ฆฝ
    • ๋ฒ•์  ๋Œ€์‘ ๋ฐ ๋ณด์•ˆ ๊ฐ์‚ฌ ์ž๋ฃŒ๋กœ ํ™œ์šฉ (์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ: ์ˆซ์ž, ๋‚ ์งœ, ์‹๋ณ„์ž ๋“ฑ ์ฟผ๋ฆฌ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋ชจ๋“  ๊ฐ’)

๋กœ๊ทธ ๊ธฐ๋ก ๋Œ€์ƒ

  • ๐ŸŒ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์š”์ฒญ ๋กœ๊ทธ: ์ž…๋ ฅ ๊ฐ’, URL, IP ๋“ฑ
  • ๐Ÿ—„๏ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—๋Ÿฌ ๋กœ๊ทธ: SQL ๋ฌธ๋ฒ• ์˜ค๋ฅ˜, ํƒ€์ž… ๋ณ€ํ™˜ ์˜ค๋ฅ˜ ๋“ฑ
  • ๐Ÿ” ์ธ์ฆ/์ธ๊ฐ€ ๊ด€๋ จ ์ด๋ฒคํŠธ: ๋กœ๊ทธ์ธ ์‹คํŒจ, ๊ถŒํ•œ ์ƒ์Šน ์‹œ๋„ ๋“ฑ

โš ๏ธ ๋กœ๊ทธ ๊ธฐ๋ก์‹œ ๊ณ ๋ ค์‚ฌํ•ญ

  • ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ ์ž…๋ ฅ๊ฐ’
    • ์‚ฌ์šฉ์ž ์ž…๋ ฅ๊ฐ’, ํŠนํžˆ ํŠน์ˆ˜๋ฌธ์ž๋‚˜ SQL ์˜ˆ์•ฝ์–ด ํฌํ•จ ์—ฌ๋ถ€ ๊ธฐ๋ก
  • ์—๋Ÿฌ ๋ฐ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋กœ๊ทธ
    • SQL ์‹คํ–‰ ์˜ค๋ฅ˜, ๊ตฌ๋ฌธ ์˜ค๋ฅ˜, ํƒ€์ž… ๋ณ€ํ™˜ ์˜ค๋ฅ˜ ๋“ฑ ์ƒ์„ธ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๊ธฐ๋ก
  • ์‚ฌ์šฉ์ž ๋ฐ ์š”์ฒญ ์ •๋ณด
    • ์š”์ฒญํ•œ IP, timestamp, User Agent ๋“ฑ ๊ณต๊ฒฉ ์ถ”์ ์— ์œ ์šฉํ•œ ์ •๋ณด ๊ธฐ๋ก

๋ฏผ๊ฐ ์ •๋ณด(์˜ˆ: ๋น„๋ฐ€๋ฒˆํ˜ธ, ๊ฐœ์ธ์ •๋ณด)๋Š” ๋กœ๊ทธ์— ๊ธฐ๋กํ•˜์ง€ ์•Š๋„๋ก ์ฃผ์˜

๋ชจ๋‹ˆํ„ฐ๋ง ๋„๊ตฌ ๋ฐ ๊ธฐ์ˆ 

  • SIEM(๋ณด์•ˆ์ •๋ณด ๋ฐ ์ด๋ฒคํŠธ ๊ด€๋ฆฌ) ์‹œ์Šคํ…œ
    • Splunk, ELK Stack (Elasticsearch, Logstash, Kibana), Graylog ๋“ฑ์„ ํ™œ์šฉํ•˜์—ฌ ๋กœ๊ทธ ์ง‘๊ณ„ ๋ฐ ์‹ค์‹œ๊ฐ„ ๋ถ„์„
  • ์•Œ๋ฆผ ๋ฐ ์ž๋™ ๋Œ€์‘
    • ํŠน์ • ํŒจํ„ด(์˜ˆ: SQL ๊ตฌ๋ฌธ ์˜ค๋ฅ˜, ์ด์ƒํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’) ๊ฐ์ง€ ์‹œ ๊ด€๋ฆฌ์ž์—๊ฒŒ ์•Œ๋ฆผ ๋ฐœ์†ก
    • ์ผ์ • ๊ธฐ์ค€ ์ดˆ๊ณผ ์‹œ ์ž๋™์œผ๋กœ ๋ฐฉ์–ด ์กฐ์น˜(์˜ˆ: IP ์ฐจ๋‹จ) ์ˆ˜ํ–‰
This post is licensed under CC BY 4.0 by the author.