PHP - Include
<?php
{php code}
?>
<?={php code}?> # short echo tag; php코드의 결과가 출력됩니다.
위와 같은 구문은 php 구문으로 해석됨.
include 함수는 인자로 전달된 파일을 읽은 후 해당 파일의 내용을 출력함.
<?php
# index.php
include $_GET["page"];
?>
<?php
# register.php
echo "This is registration page.<br>";
?>
소스 구성이 위와 같을 때, /index.php?page=register.php 로 요청하면 register.php 로드해 보여줄 수 있음.
include 는 파일에 php 코드가 있다면 파일 확장자와 상관없이 php코드가 실행됨.
include 파일 조작 가능하다면 /index.php?page=/etc/passwd 과 같이 요청하여 서버 내 정보 출력 가능.
서버 로컬 파일을 include하는 취약점 => Local File Inclusion (LFI)
외부 자원을 include하는 취약점 => Remote File Inclusion (RFI)
wrapper
메시지 앞에 놓여서 그에 관한 정보를 제공. 지정 수신자 외에 보지 못하도록 캡슐화 가능.
file:// — Accessing local filesystem
http:// — Accessing HTTP(s) URLs
ftp:// — Accessing FTP(s) URLs
php:// — Accessing various I/O streams
zlib:// — Compression Streams
data:// — Data (RFC 2397)
glob:// — Find pathnames matching pattern
phar:// — PHP Archive
file system 접근
<?php
include "file:///etc/passwd";
/*
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
*/
allow_url_include on 되어 있어야 함. (default off)
<?php
// php.ini => allow_url_include=On
include "http://example.com";
/*
<!doctype html>
<html>
...
*/
?>
off 되어 있는 경우
<?php
// php.ini => allow_url_include=Off
include "http://example.com";
/*
Warning: include(): http:// wrapper is disabled in the server configuration by allow_url_include=0
*/
echo file_get_contents("http://example.com");
// allow_url_include 영향 받지 않음.
/*
<!doctype html>
<html>
*/
?>
RFC 2397 데이터 처리
data는 allow_url_include 영향 받음
<?php
echo file_get_contents('data://text/plain;base64,SSBsb3ZlIFBIUAo=');
// I love PHP
php의 다양한 I/O stream과 filter 사용.
- php://stdin, php://stdout and php://stderr
- stdin, stdout, stderr로 연결.
- php://fd/<fd number>
- file descriptor에 연결.
- php://memory and php://temp
- 메모리에 연결.
- php://memory and php://temp
- 메모리에 연결합니다.
<?php
// Set the limit to 5 MB.
$fiveMBs = 5 * 1024 * 1024;
$fp = fopen("php://temp/maxmemory:$fiveMBs", 'r+');
fputs($fp, "hello\n");
- php://input
- HTTP의 body 즉, POST 데이터를 입력받습니다.
<?php
// HTTP Body Data
$rawData = file_get_contents("php://input");
- php://filter
- I/O 스트림에 특정 필터를 설정합니다.
<?php
// 데이터 리드 시 대문자로 처리.
include 'php://filter/read=string.toupper/resource=/etc/passwd';
/*
ROOT:X:0:0:ROOT:/ROOT:/USR/BIN/ZSH
DAEMON:X:1:1:DAEMON:/USR/SBIN:/USR/SBIN/NOLOGIN
...
*/
// 데이터 출력 시 대문자로 처리.
file_put_contents("php://filter/write=string.toupper/resource=example.txt","Hello World");
/*
$ cat example.txt
HELLO WORLD
*/
?>
read=<filter> | read 시 사용할 필터 |
write=<filter> | write 시 사용할 필터 |
resource=<stream name> | 필터링을 설정할 stream 정보 |
include 시에 php 태그 존재 시에 php가 실행되고 소스는 출력되지 않음.
- test.php
<?php echo 'test !'; ?>
- index.php
<?php
include 'test.php';
/*
test !
*/
include 'php://filter/read=convert.base64-encode/resource=test.php';
/*
PD9waHAgZWNobyAndGVzdCAhJzsgPz4=
base64_decode("PD9waHAgZWNobyAndGVzdCAhJzsgPz4="); ==> <?php echo 'test !'; ?>
*/
read filter로 base64 인코딩적용. test.php 대상
Extract
extract는 배열에서 변수를 가져옴.
<?php
$size = "large";
$var_array = array("color" => "blue",
"size" => "medium",
"shape" => "sphere");
extract($var_array);
echo "$color, $size, $shape\n"; // blue, medium, sphere
array["color"] = "blue"였지만
extract($array)하는 순간 $color가 blue가 됨.
그런데 여기에
$_GET, $_POST, $_FILES
과 같은 데이터가 채워지면 취약점 발생
특히 사용자의 입력이 인자로 들어가면 해당 인자에 매핑되는 변수값이 변경됨.
<?php
$systemCMD = "ping 127.0.0.1";
...
extract($_GET);
...
system($systemCMD);
/*
/?systemCMD=id
uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack)
*/
type juggling
서로 다른 타입인 변수를 비교 또는 연산 시 자동으로 형변환이 발생.
<?php
$a = "a"; // String
$b = "2"; // String
echo $a * 2; // 0
echo $b * 2; // 4
/*
String과 integer 비교 또는 연산 시 앞 문자열이 숫자로 시작할 경우 해당 숫자를 사용하여 연산합니다.
아닌 경우에는 0으로 처리됩니다.
*/
$a = "a";
$b = "";
var_dump($a == True); // bool(true)
var_dump($b == True); // bool(false)
/*
String과 Boolean 타입 비교 또는 연산 시 문자열의 내용이 존재하면 True, 빈 문자열인 경우 False로 처리됩니다.
*/
comparison
비교 연산자 == 를 사용할 때 비교하는 두 변수의 타입이 다르면 type-juggling에 의해 타입이 변환된 후 비교함.
정확한 비교를 위해서는 동일 타입인지도 비교하는 비교연산자 === 를 사용해야 한다.
$a == $b | Equal | type juggling 후 $a와 $b의 값이 같으면 True. |
$a === $b | Identical | $a와 $b의 타입과 값이 모두 같으면 True. |
$a != $b | Not equal | type juggling 후 $a와 $b의 값이 다르면 True. |
$a <> $b | Not equal | type juggling 후 $a와 $b의 값이 다르면 True. |
$a !== $b | Not identical | $a와 $b의 타입 또는 값이 다르면 True. |
strcmp 함수는 PHP에서 사용 시
strcmp(string $str1, string $str2)
값이 같으면 0, str1이 작으면 음수 , str2가 작으면 양수가 반환
string이 아닌 array를 인자로 주면 NULL이 반환됨.
NULL은 비교연산자 ==으로는 0과 같음. ===으로 비교해야한다.
<?php
var_dump(strcmp("a", array())); // NULL
var_dump(strcmp("a", array()) == 0); // NULL == 0 -> true
var_dump(strcmp("a", array()) === 0); // NULL === 0 -> false
Session
session 기본 문법
<?php
session_start(); // session 사용을 위해 필수적으로 사용되어야 함.
$_SESSION['uid'] = 1234; // 해당 유저의 session['uid']에 int 1234 로 할당
session_start() 수행 시
- HTTP Request에서 쿠키를 가져와 PHPSESSID(php session 이름)의 값을 가져옴.
- 만약 PHPSESSID가 없으면 새로 PHPSESSID 생성하고 세션 변수($_SESSION)에 빈 array 할당
- 만약 있으면 해당 값을 ($_SESSION)에 로드.
- $_SESSION 변수에 접근하여 값 읽거나 변경.
<?php
session_start();
$_SESSION['uid'] = 1000; // uid에 1000 할당
$_SESSION['name'] = "dreamhack"; // name에 "dreamhack" 할당
echo $_SESSION['name']; // session에 저장된 name 출력.
// dreamhack
session_start로 시작된 PHP가 종료될 때 자동으로 세션 변수를 serialize한 값을 파일 시스템에 저장함.
저장 시에는 세션기본경로/sess_PHPSESSID 값
# ls -al /var/lib/php/sessions/
total 28
drwx-wx-wt 2 root root 4096 Mar 6 04:32 .
drwxr-xr-x 4 root root 4096 Sep 2 2019 ..
-rw------- 1 www-data www-data 13 Jan 31 08:52 sess_2bahu3lkck1089v5mrr8dhd657
...
-rw------- 1 www-data www-data 32 Jan 31 10:17 sess_sr3b6npc15nu6sqsdrb959qp74
# cat sess_sr3b6npc15nu6sqsdrb959qp74
uid|i:1000;name|s:9:"dreamhack";
리눅스 시스템의 경우 세션 기본경로가 /var/lib/php/sessions 이다.
<?php
session_start();
$_SESSION['name'] = $_GET['name']; // 사용자 입력 데이터를 세션에 저장.
위와 같은 파일에서 /session.php?name=<?php system('id')?> 로 세션에 삽입
# ls -al /var/lib/php/sessions/
total 28
drwx-wx-wt 2 root root 4096 Mar 6 04:32 .
drwxr-xr-x 4 root root 4096 Sep 2 2019 ..
-rw------- 1 www-data www-data 34 Jan 31 08:52 sess_d54ejehuaq8ksuoatpeph898q3
# cat sess_d54ejehuaq8ksuoatpeph898q3
name|s:21:"<?php system('id');?>";
<?php
include $_GET['page']; // 사용자의 입력 데이터를 기반으로 include 실행.
사용자의 입력데이터 기반으로 include 실행 시 (파일을 읽음)
/index.php?page=/var/lib/php/sessions/sess_d54ejehuaq8ksuoatpeph898q3 와 같이 세션 파일 include 가능.
Upload Logic
<?php
$uploadDIR = './uploads/';
$error = $_FILES['file']['error'];
$name = $_FILES['file']['name'];
if( $error != UPLOAD_ERR_OK ) {
// error occurs
}
if( move_uploaded_file( $_FILES['file']['tmp_name'], "$uploadDIR/$name") ){
// upload success
}else{
// upload fail
}
move_upload_file을 통해 업로드 된 파일을 서버 파일 시스템에 옮김.
바로 파일 시스템 경로에 업로드 되는 것이 아니라 임시 디렉토리에 저장된 후 move_upload_file을 통해 옮겨짐
php 파일 업로드 로직
- 임시 디렉토리에 임시 파일생성
- php 로직 중에 파일 처리 로직 존재 시 임시파일 참조 $_FILES['파일 이름']['tmp_name']
- php 로직 처리 후 임시 파일 삭제
만약 php 코드에서 지연 발생 시에 임시 파일이 삭제되지 않고 존재함.
임시 파일의 파일명 규칙은 php[a-zA-Z0-9]{6}
php 파일 업로드 기능에서
- 업로드 기능이 구현되어 있지 않아도 사용자가 임시 디렉토리에 업로드 가능하다.
- 사용자의 요청 중 파일이 존재하면 임시파일을 생성하기 때문. 그리고 php엔진에 문제가 발생 시에 임시파일이 삭제되지 않기 때문에 디렉토리에 남아있다.
- 하지만 자신이 생성시킨 임시파일의 이름이 무엇인 지 알아야 하는데 같은 데이터를 가진 파일을 무한히 업로드하여 임시 파일의 파일 명 생성 규칙에서 예측 가능한 범위까지 줄일 수 있음.
'Security' 카테고리의 다른 글
[wargame] php-1 (0) | 2021.09.28 |
---|---|
[wargame] Tomcat Manager (0) | 2021.09.27 |
[Webhacking] language specific vulnerability (0) | 2021.09.27 |
[Webhacking] business logic vulnerability (0) | 2021.09.27 |
[wargame] xss-2 (1) | 2021.09.26 |