gosec은 go를 위한 정적 보안 분석 도구이다. 널리 알려진 취약점들을 대비할 수 있게 코드를 분석해주는 도구이다. gosec
이라는 명령어를 통해 코드를 분석하고 레포트를 생성할 수 있다.
설치 및 활용 예
설치방법 링크 를 참고.
여기는 바로 사용할 수 있도록 간단히 정리한다.
최신버전 설치방법:
# 아래 코드를 실행하면 최신 버전의 gosec을 설치하며 $(go env GOPATH)/bin/gosec 에 바이너리가 설치된다.
curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s -- -b $(go env GOPATH)/bin latest
다음은 정적 보안분석을 쉽게 실행할 수 있게 Makefile
로 만들어 관리하는 예시이다.
gosec
이 설치되어 있어야 동작한다.
# 정적 보안 분석 사용 예시
sast:
@mkdir -p .public/sast # .public/sast 디렉토리 생성
@gosec -fmt=html -out=.public/sast/index.html ./...; gosec -fmt=json -out=.public/sast/results.json ./...;
.PHONY: sast
참고로 ./…
는 재귀적으로 현재 디렉토리부터 모든 하위 디렉토리를 검사한다.
make sast
를 실행하면 html과 json 파일이 .public/sast
에 생성된다.
간단하게는 아래 명령만으로도 가능하다.
gosec ./...
대표적인 정적 보안 분석 예시
securego/gosec에서 제공하는 문서에서 간단히 정리해 봤다.
하드코딩된 비밀정보
potential hardcoded credentials
func main() {
password := "87abc123*af$3"
}
위 코드처럼 코드 내에 비밀번호를 직접 입력해두는 것은 위험하다. gosec
은 이런 실수를 잡아준다.
모든 네트워크 인터페이스에 바인딩
네트워크에서 0.0.0.0은 모든 IP에서 접근 가능함을 의미한다.
따라서 보안 이슈가 있을 수 있고, 이 경우, gosec
은 에러를 띄워준다.
package main
import (
"log"
"net"
)
func main() {
l, err := net.Listen("tcp", "0.0.0.0:2000")
if err != nil {
log.Fatal(err)
}
defer l.Close()
}
“0.0.0.0”으로 네트워크가 열린 것을 감지해 에러를 띄워주는 구조이다.
unsafe package
unsafe 패키지를 이용하면 low level 에서 메모리 관리를 할 수 있지만, 보안 취약점을 노출하기도 쉬워진다.
- data leak
- 메모리 오염
- 공격자의 스크립트 실행
위 세가지 이슈가 발생할 가능성이 생긴다.
체크되지 않은 error
아래 main함수처럼 실행한 함수에서 리턴된 error를 적절히 핸들링 해주지 않는 것도 분석 과정에서 검출된다.
package main
import "fmt"
func test() (int,error) {
return 0, nil
}
func main() {
v, _ := test() // return된 에러를 _로 받아 무시하고 있음.
fmt.Println(v)
}
URL을 변수에 담거나 오염될 수 있는 input에서 가져오는 경우
URL을 상수가 아닌 변수로 선언할 경우, 혹은 사용자의 input처럼 위협소지가 있는 것에서 가져올 경우 SSRF 공격 등에 취약할 수 있다.
format string이나 string concatenation을 이용해 query문을 작성하는 경우
제목은 어렵지만 예시는 쉽다.
func main() {
// format string
formatString := fmt.Sprintf("SELECT * FROM users where name = '%s'", variable)
// string concatenation
stringConcat := "SELECT * FROM users WHERE gender = " + variable
}
이런 경우 SQL Injection에 취약해지게 된다.
대안은 크게 2가지가 제시된다.
- 상수로 쿼리문을 작성해두고 활용
database/sql
패키지의 활용
// 상수 활용
const staticQuery = "SELECT * FROM users WHERE age = 10"
// database/sql
import "database/sql"
func main() {
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
panic(err)
}
rows, err := db.Query("SELECT * FROM users WHERE name = ?", name)
}
이렇게 외부 라이브러리의 argument placeholder를 사용하게 되면 적절한 escape가 자동으로 제공되기 때문에 안전해진다.
오염될 수 있는 input으로 file path를 받는 경우
- 변수에 file path를 할당하는 경우,
- user input 등으로 받는 경우
아래와 같이 /safe/path
에서 뒤로 탐색하며 /private/path
에 접근할 수 있다.
func main() {
repoFile := "/safe/path/../../private/path"
}
자세한 내용은 링크 참조.
결론
물론 gosec
이 완벽하지는 않다. 사용 사례에 따라 ‘이걸 왜 검출 못하지’ 하는 부분도 분명히 있기 때문에 너무 과신해서는 안된다. 하지만 프로그래머는 완벽하지 않기 때문에 이런 정적 분석 도구가 유용한 경우가 있다.
좀 더 세팅을 만져가며 본인의 상황에 맞게 설정할 필요가 있을 것 같다. 그렇게 쓸 경우 더욱 강력해질 것 같다.