Docs

RCE by lfi using php session file without angle bracket

개요
lfi는 local file include 의 줄임말로 공격자가 원하는 파일을 include 시킴으로써 소스코드를 leak하거나 임의의 코드를 실행시키는것이 목적이다.
include를 통해 원하는 코드를 실행시키려면 payload가 담긴 파일이 서버내에 있어야 한다.
( 서버 외부에 있다면 remote file include 공격으로 분류하며 해당 기법은 서버 설정을 심하게 탄다. )
/proc/self/environ, access_log, error_log 등 다양한 방법이 제시되었으나 권한, 셋팅등의 이슈로 성공률이 희박해 좋은 대안이 되지 못한다.

여기서 session파일을 include하는 방법이 등장한다.
실행시키고자 하는 payload를 아이디로 회원가입, 로그인을 해 공격자의 세션파일에 payload를 담고 lfi 취약점을 통해 실행하는 방법이다.
이 공격의 장점은 세션파일이 저장되는 경로의 default값은 경우의 수가 많지 않으며, 파일 이름의 규칙도 통일되어 있으므로 공격의 성공률이 높아진다는 점이다.
( 우분투와 데비안은 /var/lib/php5, 레드햇과 centos는 /var/lib/php/session, 파일 이름은 항상 sess_{$sessionid} )
또한 session을 사용하기 위해서는 php 엔진이 세션파일에 접근할 수 있어야 하므로 권한이슈에서도 자유롭다.
그러나 웹사이트에서 XSS공격에 대한 방어의 목적으로 input값을 검증해서 <, > 등의 특수문자를 html entity로 치환하는 경우에 ( 매우 흔하다 )
세션파일 내에 angle bracket 을 넣을 수 없게되고, 따라서 php 코드의 실행이 불가능해진다.
이제부터 위와 같은 방어기법을 우회하는 방법을 소개한다.

[+] 본문
특수문자가 필터링되어 있으면 payload를 base64 encode해서 넣고, 실행시에 decode하면 필터를 우회할 수 있다.
그러나 실제 공격과정에서 여러 issue가 발생한다.

우선 세션파일은 값을 serailize 해서 저장한다.
user_id|s:5:”guest”;user_level|s:1:”9”; 와 같은 형식이다.
base64 decode 알고리즘은 4바이트를 잘라 3바이트로 만들어주는데 payload의 앞에 몇바이트의 불필요한 string이 들어있는지 모른다.
XXXXaGVsbG8= 를 base64 decode하면 ]u×hello 지만,
XXXXXaGVsbG8= 를 base64 decode하면 ]u×]¡•±±¼ 라는 괴상한 값이 나오는 issue가 발생할 수 있다. ( 비루한 필력탓에 말이 괴상한데 적당히 이해하자. )
그러므로 base64 decode를 하지 않고 plaintext를 출력시켜 페이로드 앞의 string이 4의 배수인지 확인해야 한다.
4의 배수가 아니라면 payload 앞에 padding을 해줘서 4의 배수가 되도록 맞춰야한다.

또한 값을 write한 세션과 별도의 세션을 사용해 include를 해야한다.
php에서 session을 사용할 경우에 php 코드가 실행되는 동안 세션파일을 열고있게 된다.
따라서 자신의 세션파일을 include하려고 하면 php코드가 시작하자마자 내 세션파일을 열게되고, include하는 라인에서 파일을 읽을 수 없게된다.
그러니 A세션에 payload를 올려두고 B세션에서 A의 세션파일을 읽도록 해야한다.

[+] exploit

임의의 값으로 회원가입, 로그인 후에 세션파일을 include한다.
page=php://filter/convert.base64-decode/resource=../../../../../../tmp/sess_a

아래와 같은 값이 출력 될 것이다.
user_name|s:5:”user1″;

여기서 우리가 조작 가능한 부분은 빨간색의 user1 이므로 그 앞의 값 user_name|s:5:” 가 4의 배수인지 확인한다.
4의 배수가 아니라면 몇바이트의 패딩을 해야하는지 계산한다.

A세션에서 padding + PD89ZXZBTCAJKCAJJGF6QUEJKSAJCSA7ID8+ 의 값으로 회원가입, 로그인을 한다.
가끔 userid를 uppercase하는 경우도 있는데 이런 경우에 decode된 값이 전혀 달라지므로 처음부터 대문자로 payload를 작성해 보았다.
+ 까지 필터링 되어있을 경우 혹은 소문자만 사용해야하는 경우에도 약간의 노력 혹은 코딩을 통해 페이로드를 작성할 수 있을것이다.

B세션에서 해당 파일을 include한다.
page=php://filter/convert.base64-decode/resource=../../../../../../tmp/sess_a&azAA=phpinfo();

[+] 마무리
해당 기법은 막연한 아이디어만 가지고 있다가 몇주전에 모 대회 문제를 출제하면서 처음으로 구현해보았다.
개념은 단순하지만 실제 공격과정에서 엄청난 이슈가 발생해 많은 멘붕을 유발했다.
단순한 개념 설명만 적어놓았다가는 lfi를 처음 공부하는 사람들이 따라하기 힘드리라 생각해 최대한 자세히 적어보았다.

Leave a Reply

Your email address will not be published. Required fields are marked *