이런 난독화?된 php 소스들이 3003개인건데, system, eval 과 같이 웹쉘로 이용될 수 있는 함수들이 보인다.
한마디로 문제의 논지는 해커가 웹서버를 해킹하여 웹쉘을 올려놨는데 3003개중에서 웹쉘로 이용될 수 있는 아이를 찾아 실행시켜야 하는것이다.
솔직히 말하자면 이문제에 삽질 너무 했다..(많이는 아니지만)
코드를 보면 함수도 선언되어있고 system에 $_GET으로 user input을 받는데 3항 연산자로 선언하여 결국엔 user input이 system과 같은 함수에 도달하지 못하는 경우도 있고.. 주석 안에 코드가 있는 경우도 있고 뭐 이런 여러가지 난독화 기법이 걸려있었다.
나는 이를 optimization해야 하는 문제라 생각하여 주석을 지우고 쓸데없는 함수(선언되지 않고 정의만 된 함수)를 삭제하고 뭐 이런걸 했는데 optimizing을 어느정도 다 했는데 시스템 명령을 실행할 수 있는 함수가 없는것이다 (..!)
사실 optimizing을 하면서도 뭔가 '아 이렇게 고생할거면 그냥 get,post user input만 파싱해와서 쭉 브포때리면 되지 않을까' 생각은 했었는데 optimizing 자체에 목표가 생겨버려서(?) 하다보니 해답과는 멀어진것 같다.
쨌든
optimization을 하는 코드만 첨부하고 이번 문제는 마무리하겠당.
뭐 이 코드에서 get,post 파싱하는 부분도 있으니 이거 가져와서 쭉쭉 짜면 될거같은데 귀찮아서 그냥 라업보고 풀었다
#-*-coding: utf-8-*-
import requests
import sys
import urllib
import time
import sys
import struct
import os
import locale
import re
locale.getpreferredencoding()
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
with open('eval_var.txt','rb') as f:
eval_vars = f.read().split('\n')
fun_re = re.compile('function \w{1,15}\(.+\)\{.+\}',re.DOTALL)
cnt = 0
for x in os.walk('src/'):
x[2].sort()
for files in x[2]:
#print '[+] doing this.. {}'.format(files)
with open('./src/'+files,'rb') as f:
lines = f.read()
tr = ''
# removing comment
if '/*' in lines:
tr += lines.split('/*')[0]
#print lines.split('/*')
for i in lines.split('/*')[1:]:
try:
tr += i.split('*/')[1]
except:
print 1-2
lines = tr
tr = ''
# removing unuse functions
p = re.compile('function (\w|\d){1,20}\(\)')
if p.findall(lines):
tr += lines.split('function ')[0]
for i in lines.split('function ')[1:]:
fn = i.split('()')[0]
if len(re.compile(fn).findall(lines)) > 1:
tr += 'function {}\n'.format('}'.join(i.split('}\n \n')))
else:
tr += '}'.join(i.split('\n \n}')[1:])
lines = tr
# removing unuse $_GET / $_POST
p = re.compile("\$_((GET)|(POST))\[\\'.*\\'\]")
tr = ''
if p.findall(lines):
t = lines.split('\n')
for i in t:
if p.findall(i):
if re.compile("\$_((GET)|(POST))\[.*\] \?\? ' '").findall(i):
pass
elif re.compile("\$_((GET)|(POST))\[.*\] = ' ';").findall(i):
pass
elif re.compile("\$_((GET)|(POST))\[\\'.*\\'\] \?\? \\' \\'").findall(i):
pass
else:
if re.compile("echo `{\$_GET\['.*'\]}`;").findall(i):
varname = re.compile("echo `{\$_GET\['").split(i)[1].split("'")[0]
if len(re.compile(varname).findall(lines)) == 1:
print 'high availability - {} : {}'.format(files, i)
print len(re.compile(varname).findall(tr))
exit(1)
else:
for jj in tr.split('\n'):
if varname in jj:
print files
print jj
print '--------------\n'
else:
print'-----------------'
print i
print'-----------------'
print lines
exit(1)
tr += i+'\n'
else:
tr += i+'\n'
lines = tr
최근 브라우저의 자바스크립트 엔진이 ecma script 2019(es10) 까지 업데이트 되며 많은 요소와 기능들이 추가되었습니다. 따라서 기존 javascript가 아닌 최신 ecma script 표준으로 인한 xss filtering bypass 기법들이 신설되고 있습니다. 이러한 내용들에 대해 공유하고 그 원리에 대해 간단히 설명드릴까 합니다.
XSS(Cross-Site Scripting) 취약점은 주로 사용자의 unvalid input이 브라우저의 dom 내부에 삽입되거나, 모종의 javascript 동작으로 인하여 공격자가 원하는 코드를 임의로 실행시키거나, 코드 실행의 흐름을 변경하는 취약점입니다.
대다수 경우 XSS 취약점을 방어하기 위해 하기와 같은 방어기제를 둡니다.
코드단에서의 필터링
사용자에게 파라미터를 전달받아 이를 dom에 뿌려야 하는 경우 사용자의 파라미터를 검증(validation)하여 XSS 공격에 사용되는 문구나 문자가 있을 경우 요청을 거절하거나 해당 문구/문자를 삭제합니다.
대표적으로 "(Double Quote), '(Single Quote), `(Back Quote), <(Left Angle Bracket), >(Right Angle Bracket) 등의 문자, script, alert, eval, onerror 등의 HTML Tag 및 Attribute 등이 대상입니다.
XSS 필터링 솔루션을 통해 필터링 하거나, 아니면 자체적으로 구현한 XSS Filter Function을 통해 필터링 하게 됩니다.
예를들어 Java 웹 서버의 경우 <% String param1 = request.getParameter("param1").replaceAll("script", ""); %> 와 같이 대치하거나, 문자열을 찾을 경우 파라미터를 비워버리는 등 다양한 필터링 동작을 구현할 수 있습니다.
웹방화벽(WAF)에서의 필터링
코드단에서의 필터링과 동일하게 특정 문구나 문자가 있을 경우 사용자의 요청을 거절합니다.
웹방화벽에서의 필터링은 코드단에서보다 해당 서버의 페이지에 전역적으로 적용되기 때문에 일반적으로 문자에 대한 필터링보단 HTML Tag나 Attribute와 같은 문구에 대한 필터링이 강한것이 특징입니다.(경험상)
replace등의 동작은 거의 수행하지 않고 block을 통해 공격을 막는 것 같습니다.
최근 잘 방어된 사이트의 경우 거의 대부분의 HTML Tag와 Attribute가 필터링 목록에 들어가있습니다. 이런 경우엔 DOM XSS 라 불리우는 ?param="><script>alert('xss');</script> 와 같이 HTML Tag를 직접 삽입해야 하는 XSS는 대부분 필터링에 걸려 실행되지 않습니다. (추가로 이렇게 HTML TAG를 직접 입력해야 하는 경우 클라이언트 단의 브라우저 XSS Auditor에 탐지되어 일부 특수 케이스를 제외하곤 스크립트가 실행되지 않습니다. 이러한 경우 실제 공격을 수행하기 위한 weaponizing은 무리가 있습니다.)
따라서 이렇게 필터링이 심하게 걸려있는 상황에서 XSS 취약점이 발생하는 경우 중 하나로 <script> 태그 영역 내에 사용자의 입력이 "제한없이" 삽입되는 경우입니다. 여기서 말한 "제한없이"는 웹방화벽에서의 필터링은 걸려있어도 코드단에서 필터링이 존재하지 않거나 미비하여 내가 원하는 코드 흐름을 만들 수 있는 경우를 뜻합니다. 예시와 함께 설명하겠습니다.
XSS Vuln Code Example
<script>
var username = "<%=request.getParameter("username")%>";
alert(username+"님 로그인 하셨습니다.");
</script>
위와 같은 경우 username 파라미터에 "(Double Quote) 문자를 삽입하여 script 태그 내의 문자열에 삽입되던 파라미터가 문자열을 벗어나고 임의 스크립트를 실행시킬 수 있게 됩니다.
하지만 이는 필터링이 하나도 없는 경우이고 코드단 필터링이나 웹방화벽에서의 필터링이 존재한다면 해당 문자 및 문구를 사용하지 않고 코드흐름을 제어해야 합니다.
본 글에선 해당 상황에서 쓰일 수 있는 유용한 필터링 우회 구문들을 몇가지 소개하고자 합니다.
alert 등, javascript 내장함수의 실행을 정규식으로 필터링하는 경우
a=alert; a(document.domain);
javascript에서의 함수(Function)는 Object의 종류 중 하나입니다. javascript에서의 Object는 객체로써 변수에 해당 Object를 할당해줄 수 있습니다.
따라서 변수 a에 alert (내장함수)를 할당하고, 이를 호출함으로써 alert(document.domain)을 위와 같이 표현할 수 있습니다.
정규식을 통해 (ex. /alert(.+)/) 특정 함수의 실행을 막고있다면 위와같이 우회가 가능합니다.
URL은 내장함수입니다. 함수(Function)는 javascript에서 Object입니다. Object와 String 타입간의 더하기 연산이 일어났을 땐 Object.toString() 메서드를 우선 실행하여 Object 자체를 문자열로 Type Casting 한 후 String 문자열과 더해줍니다.
따라서 위의 그림과 같이 문자열로 변하게 됩니다. 이와같은 동작을 수행한 이유는 괄호가 필터링 되어있기 때문에, 괄호를 쓰지 않고 괄호 문자를 얻기 위해서입니다.
_ 변수에 해당 문자열이 할당되었습니다. _[12] == (, _[13] == )
위에서 살펴봤듯이, 정규식을 이용하여 문자열을 획득 및 더해줄 수 있습니다. 이러한 연산을 통해 "alert(document.domain)"이라는 문자열을 얻었습니다.
최신 Ecma Script에선 기존 String, Integer 등 자료형 외에 Symbol이라는 자료형이 신설되었습니다.
이 Symbol 자료형의 속성 중 hasInstance라는 속성이 존재합니다. 해당 Symbol 객체가 instance 인지 판단하여 이후 동작을 재정의 할 수 있는 속성입니다.
open 내장 함수가 toPrimitive method를 거치며 open(x => /javascript:0/.source+location.search) -> x = function(x){return /javascript:0/.source+location.search}; open(x); ->open(/javascript:0/.source+location.search)와 같은 구문으로 변하게 됩니다.
공격자가 uri의 링크를 https://ar9ang3.com/test.html?0:alert('xss')//¶m1=foo¶m2=bar 와 같이 설정 후 xss를 트리거했다면,
location.search의 값은 ?0:alert('xss')//¶m1=foo¶m2=bar가 되고, 전체 문자열은 javascript:0?0:alert('xss')//¶m1=foo¶m2=bar가 되게 됩니다.
0?0:alert('xss') 이후 문자열은 주석으로 인해 사라지고, 이는 3항연산의 표현식이기때문에 false?false:alert('xss')로 변하게 되어 결국 공격자가 원하는 자바스크립트 구문이 실행되게 됩니다.
지금까지 위에 적힌 구문들 외에도 무수히 많은 방법으로 필터링을 우회할 수 있습니다. 혹 새로운 우회기법이 생각나셨다면 공유해주시면 감사하겠습니다.