-
[AWS] AHSS 1주차 - S3 취약점 및 보안(feat. s3game)Infra/CloudSecurity 2023. 9. 1. 21:08
오랜만에 Gasida님 스터디에 참가하게 되었다.
Gasida님의 테라폼 스터디도 참가하고 싶었지만.. 요즘 프로젝트가 너무 바빠져서..ㅠ(다음기회에 꼭!!..)
이번 스터디의 주제는 드디어 클라우드 Security이다!
1주차 주제는 s3 취약점 및 보안으로 스터디 공백기간 동안 클라우드 관련 wargame들을 풀어본적이 있었다
(BigIAMchallange, flAWS 등)
그래서인지 훨씬 수월했고 1주차 과제는 스터디에서 배운 것들을 활용할 수 있는 s3game writeup을 진행하였다.
s3game Writeup
< Level1 >
=> 1번 문제는 그저 s3 URL의 구조에 대해서 설명을 해주고 bucket name이 뭔지만 알 수 있으면 풀 수 있는 문제였다. 대놓고 bucket name이 s3game-level1이기 때문에 aws-cli를 통해 접근해보았다.
aws s3 ls s3://s3game-level1 aws s3 cp s3://s3game-level1/tresure1 -
=> 해당 s3는 public으로 공개되어있었고 treasure1라는 object가 누가봐도 다음 Level로 갈 수 있게 도와줄 것 같이 생겼다
= >해당 파일을 확인해보니 secret code라는 숫자+영문 조합과 다음 Level로가는 링크를 알려주었다. 1번은 쉽게 클리어~
< Level2 >
=> 2번문제의 내용은 Virtual Hosted Style URL format에 대한 내용을 가르쳐주면서 이 게임에서는 Path Style의 접근은 사용하지 않으며 2020년 09월 30일 이후 생성된 bucket은 Virtual Hosted Style만 지원한다고 하며 해당 날짜보다 이전에 생성된 s3는 두 가지 Style을 모두 지원한다고 한다.
'Old' vs 'New' 참고
=> 해당 문제에서 제공하는 또하나의 링크를 들어가면 Path-style과 Virtual-hosted style의 예시를 제공하고 있다. 문제에서 보여주었던 URL은 Virtual-hosted style인 것으로 보여진다. 그렇다면 두 가지 모두 접근해보자.
Path-style & Virtual-hosted style Request
curl http://s3game-level2.s3.us-east-2.amazonaws.com/treasure2 curl https://s3-us-east-2.amazonaws.com/s3game-level2/treasure2
=> 문제에서 제공한 bucket-name은 s3game-level2이며 treasure2를 얻을 수 있을 것이라고 했으니 object 명은 treasure2를 사용해주었고 나머지는 제공하는 style의 format에 맞게 진행했다.
=> 결과는 똑같이 나왔고 웹에서 해당 URL로 접근하면 treasure2 파일이 자동으로 Download 되었다. 파일 내용은 aws credential로 보여지는 내용과 Level3으로 가는 URL을 알려주었다. credential은 다음 문제에서 쓰이지 않을까 싶다. 2번도 어렵지 않게 클리어~
< Level3 >
=> Level2에서 줍줍한 credential을 사용해야한다는 것을 직감했다. 해당 credential만이 문제에서 제공하는 bucket에 접근할 수 있을 것 같다.
aws configure
aws configure --profile test aws --profile test sts get-caller-identity # Credential 정보 Access Key ID: AKIAZBIEGK7G53TU2K4L Secret Access Key: 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 region name: us-east-2
=> 참고로 등록된 credential에 대한 파일은 .aws/credential과 .aws/config 파일에서 확인할 수 있다. 문제에서 제공하는 credential외에 나의 계정의 credential이 있어서 --profile 옵션을 사용해주었다.
=> 등록이 완료되면 Security Token Service의 get caller identity로 제대로 된건지 확인해보면 된다.
aws-cli를 통한 s3 접근
aws --profile test s3 ls s3://s3game-level3 aws --profile test s3 cp s3://s3game-level3/treasure3_has_no_secret_code -
=> 해당 credential로 s3 bucket 이름을 추측해서 접근해보았다. 기존에 s3game-<LEVEL NAME> format이였기 때문에 s3game-level3 bucket이 존재한다면 Object Listing이 될 것이라고 생각했고 예상대로 존재했다. object 중에는 treasure3가 존재했고 내용을 확인해보니 Level4로 가는 URL이 확인되었으나 중간에 <THE CODE>를 비우고 줘서.. 접근이 불가했다.
aws-cli의 s3api 명령을 통한 접근
aws s3api --profile test get-object --bucket s3game-level3 --key treasure3_has_no_secret_code outfile https://s3game-level4-k73045aztqln.s3.us-east-2.amazonaws.com/level4.html
=> 스터디에서도 언급되었던 내용으로 aws-cli 명령어 중 s3 관련 명령어는 s3와 s3api가 존재한다.
=> 둘의 차이는 s3 명령의 경우 일반적인 bucket 관련 작업이나 object 관련 작업을 쉽게 수행하기 위한 용도이며 s3api명령어는 s3 명령어 보다 좀더 많은 작업이 가능한 s3 API 요청관련 명령어이다. 즉, 명령어 그대로 s3의 API를 사용하는 것이라고 보면 된다.
=> 때문에 두 명령어는 위처럼 응답도 다르게 온다. 위에서처럼 s3api 명령어를 통해 secret code를 확인할 수 있었다. 결과적으로 이번문제에서는 s3, s3api 명령어 둘 다 사용해야 문제를 풀 수 있었다.
< Level4 >
=> 사악하다...힌트 없다고 한다. 한 가지 신경쓰이는 부분은 이번 Level의 bucket name을 더블 체크하라고 한다...
일단 s3 bucket에 접근해보자
aws --profile test s3 ls s3://s3game-level4-k73045aztqln aws --profile test s3 cp s3://s3game-level4-k73045aztqln/treasure4_also_has_no_secret_code -
=> 오잉? 그냥 바로 실마리가 나왔다. 이번에도 이전문제와 동일하게 다음 Level로 가는 URL에서 비어있는 부분을 찾기만 하면 될 것 같다. metadata를 언급하는 걸로 봐서는 s3api를 한번 더 사용해보는 것이 좋을 것 같다.
s3api 명령어 get-object 사용
aws s3api --profile test get-object --bucket s3game-level4-k73045aztqln --key treasure4_also_has_no_secret_code outfile https://s3game-level4-k73045aztqln.s3.us-east-2.amazonaws.com/treasure4_also_has_no_secret_code
=> 음.. Metadata가 빈 값으로 나왔다.. 하지만 TagCount가 1로 존재했다. 그렇다면 Tag 관련 정보를 보기로 했다.
s3api 명령어 get-object-tagging 사용
aws s3api --profile test get-object-tagging --bucket s3game-level4-k73045aztqln --key treasure4_also_has_no_secret_code # secret-code를 넣은 다음 Level로 가는 URL https://s3game-level5-8v95e5rv7z4i.s3.us-east-2.amazonaws.com/level5.html
=> 객체의 Tag 정보를 반환하는 명령을 통해 확인을 해보니 secret_code가 확인되었다. 이를 통해 다음 Level로 가는 URL의 비어진 부분을 알 수 있었다.
< Level5 >
=> 보이지 않는 것을 볼필요가 있다는 말만 남기고 다른 힌트는 없었다.
s3 ListObject
aws --profile test s3 ls s3://s3game-level5-8v95e5rv7z4i
=> 우리가 찾는 실마리가 될만한 object는 보이지 않았다. 이전에 클라우드나 컨테이너 관련 취약점 공부를 했을 때 git, docker image의 과거 데이터에 정보가 남겨져 있어 취약했던 경험이 있어서 s3도 history를 확인해야 겠다는 생각이 들었다.
s3 object version 정보 확인
aws --profile test s3api list-object-versions --bucket s3game-level5-8v95e5rv7z4i
=> 과거의 기록에서 "treasure5_is_deleted"라는 객체명이 확인되었다. 해당 객체는 DeleteMarkers가 설정되어있어 드디어 실마리를 찾았고 복구를 하거나 해당 내용을 볼 수 있는 방법을 찾아보았다.
Version 옵션값을 s3 object 조회
aws --profile test s3api get-object --bucket s3game-level5-8v95e5rv7z4i --key treasure5_is_deleted --version-id 344PQOyFqocF0TI66MbLynNNdQqHfBz3 outfile
=> 삭제되었던 객체의 Version-Id를 옵션값으로 get-object 명령을 통해 객체를 다운로드 했다. 내가 설정한 파일명인 "outfile"을 확인할 수 있었으며 해당 객체에는 역시 다음 Level로 가는 URL이 존재했다.
Bucket Version 관리 활성화 여부 확인
aws --profile test s3api get-bucket-versioning --bucket s3game-level5-8v95e5rv7z4i
=> 다 풀고 나서 다른사람의 풀이를 보고 알게된 것은 나의 경우 "Bucket의 버전관리가 활성화되어있는지"에 대한 확인과정 없이 풀이를 진행했다는 것이다. 사실 그냥 문제풀 때는 해당 과정 없이 시도해보면 되긴하지만 나중에 공격 관련 자동화 스크립트를 만든다면 버전관리 활성화 여부 확인 시, 필요할 것 같아서 작성했다.
< Level6 >
=> 이번 문제는 객체에서 필요한 데이터만 추출하여 비용을 절감할 수 있는 S3 Select에 관련된 문제라는 것을 대놓고 알려주고 있다. 심지어 문제에서 s3의 select-object-content를 사용하라고 까지 말해주는 것으로 보아, 해당 명령을 통해 문제를 풀며 사용법을 익히라는 것 같다.
s3 Object 확인
aws --profile test s3 ls s3://s3game-level6-vjv45x1gux81 aws --profile test s3 cp s3://s3game-level6-vjv45x1gux81/s3select.csv.gz .
=> s3game-level6-vjv45x1gux81 bucket의 object를 확인해보니 s3select.csv.gz라는 압축파일이 존재했고 확인을 위해 우선 Download하였다.
s3select.csv.gz 파일 살펴보기
=> 해당 파일과 문제에서 제공한 query를 비교해보면 첫 번째줄이 컬럼이라는 것을 알 수 있었고 우리가 찾는 데이터는 Answer 컬럼에 존재한다는 것을 추측해볼 수 있다. 또한 해당 칼럼내의 "TREASURE"인 부분만 반환하도록 쿼리를 작성한 것도 알 수 있었다.
=> 즉, s3 객체 내에서 Category 컬럼의 값이 "TREASURE"일 때 Answer 컬럼의 값을 출력해내면 된다.
=> 때문에 해당 파일에 유추를 통해 grep을 여러번 이용하더라도 문제는 풀 수 있었다. 하지만 이번문제의 출제의도는 select-object-content를 통해 깔끔하게 필요한 추출하는 것일 것이기 때문에 s3 select-object-content를 사용해보자.
s3 select-object-content 사용
# FileHeaderInfo": "USE" 사용 시 aws --profile test s3api select-object-content \ --bucket s3game-level6-vjv45x1gux81 \ --key s3select.csv.gz \ --expression "SELECT Answer FROM s3object WHERE Category = 'TREASURE'" \ --expression-type 'SQL' \ --input-serialization '{"CSV": {"FileHeaderInfo": "USE", "FieldDelimiter": ";"}, "CompressionType": "GZIP"}' \ --output-serialization '{"CSV": {}}' "output.csv" # FileHeaderInfo": "IGNORE" 사용 시 aws --profile test s3api select-object-content \ --bucket s3game-level6-vjv45x1gux81 \ --key s3select.csv.gz \ --expression "SELECT s._8 FROM s3object s WHERE s._5 = 'TREASURE'" \ --expression-type 'SQL' \ --input-serialization '{"CSV": {"FileHeaderInfo": "IGNORE", "FieldDelimiter": ";"}, "CompressionType": "GZIP"}' \ --output-serialization '{"CSV": {}}' "output2.csv"
=> 나머지 옵션은 사용법이 어렵지 않기 때문에 핵심 옵션만 설명하자면 --expression과 --input-serialization만 잘 넣어주면 된다. --expression의 경우 우리가 흔히 사용하는 SQL query이며 s3 객체 자체를 대상으로 데이터를 선택하는 것이기 때문에 FROM 다음에 "s3object" 키워드를 사용하면 된다. 또한 --input-serialization을 통해 FileHeaderInfo를 "USE" 나 "IGNORE"로 주어서 첫 번째 줄이 헤더임을 인식시켜주고 FieldDelimiter를 통해 레코드를 ";"로 구별할 수 있도록 작성해주면 된다.
=> 위의 방법대로 명령을 진행하게되면 깔끔하게 다음 Level로 갈 수 있게 된다.
< Level7 >
=> 이번 문제의 s3의 모든 객체가 private라고 한다. 그리고 해당 object owner만이 접근 권한이 있다고 한다. 하지만 다른 사용자들에게 share가 가능하다며 presigned URL이라는 것을 통해 시간 제한으로 object를 다운로드 할 수 있는 권한을 부여할 수 있다고 한다.
=> 해당 기능 관련해서는 스터디 시간에도 실습했기 때문에 어렵지 않게 풀 수 있었다.
s3 presigned URL
aws --profile test s3 presign s3://s3game-level7-zhovpo4j8588/treasure7 --expires-in 3600
=> 다음과 같이 해당 s3의 presigned url을 획득했다.
presigned URL로 접근
=> 해당 URL로 접근했지만 Access Denied 당했다.. 그렇다면 object명이 잘못된 것이 아닌가 싶다...
curl $(curl $(aws --profile test s3 presign s3://s3game-level7-zhovpo4j8588/somethingstrange --expires-in 3600))
=> 문제를 제대로 안봤다.. object명을 문제에서 제공한 somethingstrange로 다시 시도해 보았고 다음 Level로 가는 URL을 만날 수 있었다. 문제 잘 읽자...
< Level8 >
=> 해당 문제는 CloudFront에 관련된 문제이며 Hint로 Bucket에 대한 IAM policy를 보여주고 있다.
=> 해당 IAM 정책은 CloudFront의 경우 모든 s3 bucket에 대해 GetObject가 가능하다는 의미이다. 간략하게 말해서 CloudFront만이 s3 bucket의 object에 접근이 가능한 것이다. 이를 가능하게 하기위해 문제에서는 CloudFront distribution URL도 제공해주었다.
=> 사용자는 접근하지 못하는 S3 bucket에 대해 CloudFront를 A서버, S3 bucket object를 B서버라고 한다면 A서버인 CloudFront의 권한을 이용하여 B서버인 s3 bucket object에 접근하는 SSRF라고 볼 수 있다(예시가 애매할 순 있지만 이해를 위해..ㅎㅎ)
=> 어쨌든 특히 클라우드에서는 이러한 취약점이 발생하지 않도록 IAM policy를 잘 생각하고 구성해야한다.
s3 object Listing
aws --profile test s3 ls s3://s3game-level8-v6g8tp7ra2ld
=> 우선 Level8의 bucket object를 확인해보니 "treasure8_CDN"이라는 object가 보였다.
=> 문제에서 말한 "bucket에 대해 직접 접근을 막았다면 object를 Listing하는 것도 허용하지 말라고" 한 이유가 여기 있었다. 결국에 해당 bucket은 Cloudfront를 통해 서비스 중이므로 object명을 Listing을 통해 알게 되었으니 Cloudfront URL을 통해 object에 접근이 가능하기 때문이다.
CloudFront URL을 통해 S3 object 접근
curl d2suiw06vujwz3.cloudfront.net/treasure8_CDN
=> 아주 손쉽게 CloudFront URL로 해당 object에 접근하여 다음 Level로 갈 수 있게 되었다~
< Level9 >
=> 이번 문제는 referer에 관련된 문제로 IAM policy를 보면 aws:Referer가 "http://s3game.treasure"이여야 "s3game-level9-781xtls2quvy/treasure9_referer"를 GetObject 할 수 있다.
curl 명령어의 referer 옵션을 통한 요청
curl -e "http://s3game.treasure" https://s3game-level9-781xtls2quvy.s3.us-east-2.amazonaws.com/treasure9_referer
=> 문제에서 제공한 IAM Policy를 allow하기 위해 curl에 -e 옵션을 통해 referer값을 "http://s3game.treasure"로 맞춰주고 s3 bucket을 URL 형식으로 변환하여 요청하면 다음 Level로 갈 수 있게 된다 ~
< Level10 >
=> Amazon S3는 용도에 따라 사용할 수 있게 다양한 storage classes를 제공하고 있다고 하며 대략적인 설명을 함께 제공하고 있다.
s3 bucket sync 이용
aws --profile test s3 sync s3://s3game-level10-gac6tf83erp6 .
=> 음.. 우선은 sync 명령을 통해 다운로드 하였더니 해당 bucket에서 2개의 파일만이 다운로드 되었다. 그 중 하나는 문제 페이지이고 나머지하나가 바로 우리가 찾던 다음 Level로 가는 정보가 들어있는 object 였다. 근데 풀고나서 드는 생각은 "문제의 힌트는 하나도 사용하지 않았는데 이렇게 푸는게 맞나???" 였는데.. 아니나 다를까 출제 의도는 이렇게 푸는게 아니였다.. 크흠...
s3api list-object에 --query 사용
aws --profile test s3api list-objects --bucket s3game-level10-gac6tf83erp6 --query 'Contents[?StorageClass == `STANDARD_IA`]' aws --profile test s3 cp s3://s3game-level10-gac6tf83erp6/djq30a807iyq -
=> s3api의 list-object에 StorageClass가 "STANDARD_IA"인 것을 조회하였더니 하나의 object만 확인할 수 있었다. 출제자의 의도는 이렇게 푸는 것이 맞는 것 같다...ㅎㅎ 출제자는 아마도 수많은 object를 다 확인하지 않을거라고 생각한 것 같지만 하나의 object만 접근이 가능해서 아주 쉽게 날로 풀었다.. 아니면 문제를 위해서 알면서도 뒀을지도..
< Level11 마지막 문제 >
=> 이번 문제에서는 평문파일의 object를 안전하게 지키기위해 암호화하는 기능인 SSE(Server-Side Encryption)에 대해서 말하고 있다. 참고로 AWS에서는 S3 암호화 옵션을 활성화하면 자동으로 수행해준다.
=> 문제에서는 암호화에 사용된 Server-Side Encryption Key 값을 알려주고 있기 때문에 GetObject 시, 옵션으로 사용하면 --sse-c-key를 통해 object를 복호화하여 다운로드 할 수 있다. SSE는 SSE-S3 또는 SSE-KMS 키를 암호화 옵션을 지정할 수 있으며 둘다 AES256 알고리즘을 사용하여 데이터를 암호화 한다.s3 bucket object Listing & get object
aws --profile test s3 ls s3://s3game-level11-djq30a807iyq aws --profile test s3 cp s3://s3game-level11-djq30a807iyq/treasure11_encryption -
=> 해당 bucket에는 treasure11_encrpytion이라는 의심스러운 object가 존재했다. 내용을 보고 싶었지만 400 Error로 (이렇게 간단하게 풀릴리가 없기 때문에) 당연히 실패했다. 그렇다면 문제에서 제공한 --sse-c랑 --sse-c-key 옵션을 통해 Server-side key값을 사용해보자.
Server-side key 값을 이용하여 s3 get object 진행
aws --profile test s3 cp s3://s3game-level11-djq30a807iyq/treasure11_encryption - --sse-c AES256 --sse-c-key "UkXp2s5v8y/B?E(H+MbPeShVmYq3t6w9"
=> 마지막으로 가는 URL까지 알아낼 수 있었다. 마지막 문제는 생각보다 쉽게 끝났다~
< Level12 모든 Treasure을 다모았다>
=> 이렇게 해당 s3game 11문제를 ALL Clear 했다~ s3에 전반적인 부분들을 익힐 수 있는 좋은 문제들이였다~
반응형'Infra > CloudSecurity' 카테고리의 다른 글
[AWS] AWS Goat module1 - Insecure Direct Object Reference (0) 2023.10.20 [AWS] AWS Goat module1 - Reflected XSS (0) 2023.10.20 [AWS] flAWS2 Challenge - Level3(Attacker) (0) 2023.07.20 [AWS] flAWS2 Challenge - Level2(Attacker) (0) 2023.07.19 [AWS] flAWS2 Challenge - Level1(Attacker) (0) 2023.07.19 댓글