GO Code Snippets Review

Last updated on

Description

GO Code Snippets Review

Arbitry File Read

func uploadFile(w http.ResponseWriter, r *http.Request) {
    file, header, err := r.FormFile("file")
    if err != nil {
        fmt.Println("Error Retrieving the File")
        return
    }
    defer file.Close()
    tempFile, err := ioutil.TempFile("/tmp", header.Filename)
    if err != nil {
        fmt.Println(err)
        return
    }
    fileBytes, err := ioutil.ReadAll(file)
    if err != nil {
        fmt.Println(err)
        return
    }
    tempFile.Write(fileBytes)
    defer tempFile.Close()
    fmt.Fprintf(w, "Successfully Uploaded File\n")
}

in line number 8 is vuln to LFI.

No Delimiter

func buildSignatureforPayment(user string, amount int) (string){
  mac := hmac.New(sha256.New, []byte(key))
  mac.Write([]byte(user+strconv.Itoa(amount)))
  return hex.EncodeToString(mac.Sum(nil))
}

func buildUrl(user string, amount int, sign string) (string) {
  ret := base_url+"?user="+user
  ret+= "&amount="+strconv.Itoa(amount)
  ret+="&sign="+sign
  return ret
}

func verifyPayment(user string, amount int, sign string) (bool) {
  mac := hmac.New(sha256.New, []byte(key))
  mac.Write([]byte(user+strconv.Itoa(amount)))
  expected := mac.Sum(nil)
  provided, err := hex.DecodeString(sign)
  if err != nil {
    return false
  }
  return hmac.Equal(expected, provided)
}

in line 16 no delimiter is present so user1 who wants 999 is same as user who wants 1999.

Delimiter injection

func SignforPayment(user string, amount int)(string, string){
  data := user+":"+strconv.Itoa(amount)
  mac := hmac.New(sha256.New, []byte(key))
  mac.Write([]byte(data))
  return data, hex.EncodeToString(mac.Sum(nil))
}

func verifyPayment(data string, sign string) (bool) {
  parts := strings.Split(data,":")
  user,amount :=  parts[0], parts[1]
  mac := hmac.New(sha256.New, []byte(key))
  mac.Write([]byte(user+":"+amount))
  expected := mac.Sum(nil)
  provided, err := hex.DecodeString(sign)
  if err != nil {
    return false
  }
  return hmac.Equal( expected, provided)
}

i can inject delimiter and change the data.

Auth issue

func auth(fn http.HandlerFunc) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    user, pass, _ := r.BasicAuth()
    if !check(user, pass) {
       w.Header().Set("WWW-Authenticate", "Basic realm=\"MY REALM\"")
       http.Error(w, "Unauthorized.", 401)
       return
    }
    fn(w, r)
  }
}

func check(u, p string) bool {
  password:= os.Getenv("MYPASSWORD")
  user:= os.Getenv("MYUSER")
  if u == user || p== password {
    return true 
  }
  return false
} 

func handler(w http.ResponseWriter, r *http.Request) {
    [...]
}

func main() {
    http.HandleFunc("/",auth(handler))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

u == user || p== password is messed up.

Return Issue

func auth(fn http.HandlerFunc) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    user, pass, _ := r.BasicAuth()
    if !check(user, pass) {
       w.Header().Set("WWW-Authenticate", "Basic realm=\"MY REALM\"")
       http.Error(w, "Unauthorized.", 401)
    }
    fn(w, r)
  }
}

func check(u, p string) bool {
  password:= os.Getenv("MYPASSWORD")
  user:= os.Getenv("MYUSER")
  if u == user && p== password {
    return true 
  }
  return false
} 

func handler(w http.ResponseWriter, r *http.Request) {
    [...]
}

func main() {
    http.HandleFunc("/",auth(handler))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

No return after line 6 so flow will continue.

Sigining oracle

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "net/url"
)

var key string ="[...]"
var base string = "https://pentesterlab.com/"

func sign(user string) (string){
  mac := hmac.New(sha256.New, []byte(key))
  mac.Write([]byte(user))
  return hex.EncodeToString(mac.Sum(nil))
}

func buildReferralLink(user string) (string) {
  return base+"referral?user="+url.QueryEscape(user)+
    "&sign="+sign(user)
}


func buildPasswordResetLink(user string) (string) {
  return base+"password_reset?user="+url.QueryEscape(user)+
    "&sign="+sign(user)

}

on line 25 same sign is being used for referral link.

Zip issues

func Unzip(src string, dest string) ([]string, error) {
    var filenames []string
    r, err := zip.OpenReader(src)
    if err != nil { return filenames, err }
    defer r.Close()
    for _, f := range r.File {
        fpath := filepath.Join(dest, f.Name)
        filenames = append(filenames, fpath)
        if f.FileInfo().IsDir() {
            os.MkdirAll(fpath, os.ModePerm)
            continue
        }
        outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
        if err != nil { return filenames, err }
 
        rc, err := f.Open()
 
        if err != nil { return filenames, err }
 
        _, err = io.Copy(outFile, rc)
        outFile.Close()
        rc.Close()
 
        if err != nil {
            return filenames, err
        }
    }
    return filenames, nil
}

fpath := filepath.Join(dest, f.Name) trusting the name from zip.

Filter bypass

func isSignatureValid(value string, signature string) bool {
  ... 
}

func process(value string) {
  ... 
}

func handler(w http.ResponseWriter, r *http.Request) {
  value := r.URL.Query()["value"][0]
  signature := r.URL.Query()["signature"][0]

  if signature !=""  && !isSignatureValid(value, signature) {
    http.Error(w, "Invalid Signature.", 403)
     return
  }
  process(value)
}

func main() {
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

if we give an empty signature we can bypass the filter.

Timing issue

package main

import (
    "fmt"
    "strconv"
    "strings"
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
)

[...]

func verifyPayment(user string, amount int, sign string) (bool) {
  mac := hmac.New(sha256.New, []byte(key))
  if (strings.Contains(user ,":")) {
    return false 
  }
  mac.Write([]byte(user+":"+strconv.Itoa(amount)))
  expected := hex.EncodeToString(mac.Sum(nil))
  return expected == sign
}

[...]

== in go isn’t time constant so it will take more time to process the request if the output is correct.

Directory traversal

package main

import (
    "fmt"
    "path"
    "log"
    "net/http"
    "io"
    "os"
)

func handler(w http.ResponseWriter, r *http.Request) {
    filename := path.Clean(r.URL.Query()["filename"][0])
    fd, err := os.Open(filename)
    defer fd.Close()
    if err != nil {
      http.Error(w, "File not found.", 404)
      return
    }
    io.Copy(w, fd)
}

func main() {
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

path.Clean will remove path traversal stuff.

func filter(r *http.Request) (err error)  {
  if (strings.Contains(r.URL.Query().Get("path"), "..")) {
    return errors.New("Invalid path")
  }
  return nil
}
func serveFile(w http.ResponseWriter, r *http.Request) {
  path:= r.FormValue("path")
  fd, err := os.Open("/srv/files/"+path)
  defer fd.Close()
  if err != nil {
    http.Error(w, "File not found.", 404)
    return
  }
  io.Copy(w, fd)
}
func handler(w http.ResponseWriter, r *http.Request) {
  err := filter(r)
  if err!=nil {
    http.Error(w, "Invalid path.", 404)
    return
  } else {
    serveFile(w,r)
  }
}

path isn’t sanitized so it may contain ../

insecure randomness

import (
    "fmt"
    "math/rand"
    "errors"
    "time"
)

/* secret is a randomly generated string used when signing a JWT */
var secret string

/* init creates the random secret. */
func init() {
    rand.Seed(time.Now().UnixNano())
    /* Create a random string of random length for our secret */
    randombytes := randomBytes(rand.Intn(64))
    if randombytes == nil {
        panic(errors.New("Error creating random secret"))
    }
    secret = string(randombytes)
}

func randomBytes(n int) []byte {
    b := make([]byte, n)
    _, err := rand.Read(b)
    if err != nil {
        return nil
    }
    return b
}

rand.Seed(time.Now().UnixNano()) is insecure coz the time can be bruteforced.