CVE-2023-33965 (Go, published 2023-06-01T15:15Z)
Description: value: Brook is a cross-platform programmable network tool. The `tproxy` server is vulnerable to a drive-by command injection. An attacker may fool a victim into visiting a malicious web page which will trigger requests to the local `tproxy` service leading to remote code execution. A patch is available in version 20230606.
Severity: nan (CVSS v3 base score: 8.8)
Weakness: CWE-78 \u2014 Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
Repository: https://github.com/txthinking/brook
Files changed:
cli/brook/main.go
cli/brook/static/index.html
quicserver.go
streamclient.go
Commit message: tproxy web auth


Vulnerable code:
				s, err := brook.NewQUICServer(":"+p, c.String("password"), h, c.Int("tcpTimeout"), c.Int("udpTimeout"), c.String("blockDomainList"), c.String("blockCIDR4List"), c.String("blockCIDR6List"), c.Int64("updateListInterval"), c.StringSlice("blockGeoIP"), c.Bool("withoutBrookProtocol"))
					Usage: "Your local and server must support IPv6 both",
						log.Println(s + " tproxy " + r.FormValue("args"))
                            enableIPv6: localStorage.getItem("enableIPv6") ? true : false,
                            var r = await fetch("/status");
                                if (this.enableIPv6) {
                                    s += ` --enableIPv6`;
                                    localStorage.setItem("enableIPv6", "true");
                                    localStorage.setItem("enableIPv6", "");
                                var r = await fetch(`/start?args=${encodeURIComponent(s)}`);
                                var r = await fetch(`/stop`);
        <main>
                <label>--blockDomainList Suffix match mode</label><br />
                <input v-model="blockDomainList" placeholder="/path/to/local/file/example_domain.txt" />
                <label>--enableIPv6 Your local and server must support IPv6</label><br />
                <input type="checkbox" v-model="enableIPv6" />
func NewQUICServer(addr, password, domain string, tcpTimeout, udpTimeout int, blockDomainList, blockCIDR4List, blockCIDR6List string, updateListInterval int64, blockGeoIP []string, withoutbrook bool) (*QUICServer, error) {
	if err := c.WriteL(4 + len(dst)); err != nil {
			l, err := c.ReadL()
		if err := c.WriteL(l); err != nil {
func (c *StreamClient) WriteL(l int) error {
func (c *StreamClient) ReadL() (int, error) {

Fixed code:
				s, err := brook.NewQUICServer(":"+p, c.String("password"), h, c.Int("tcpTimeout"), c.Int("udpTimeout"), c.Bool("withoutBrookProtocol"))
					Usage: "deprecated",
					m.HandleFunc("/hasp", func(w http.ResponseWriter, r *http.Request) {
						lock.Lock()
						defer lock.Unlock()
						_, err := os.Stat("/tmp/.brook.web.password")
						if os.IsNotExist(err) {
							w.Write([]byte("no"))
							return
						}
						w.Write([]byte("yes"))
					})
					m.HandleFunc("/setp", func(w http.ResponseWriter, r *http.Request) {
						lock.Lock()
						defer lock.Unlock()
						_, err := os.Stat("/tmp/.brook.web.password")
						if !os.IsNotExist(err) {
							http.Error(w, "file exsits", 500)
							return
						}
						err = ioutil.WriteFile("/tmp/.brook.web.password", []byte(r.FormValue("p")), 0600)
						if err != nil {
							http.Error(w, err.Error(), 500)
							return
						}
						w.WriteHeader(200)
					})
					m.HandleFunc("/authp", func(w http.ResponseWriter, r *http.Request) {
						lock.Lock()
						defer lock.Unlock()
						b, err := ioutil.ReadFile("/tmp/.brook.web.password")
						if err != nil {
							http.Error(w, err.Error(), 500)
							return
						}
						if string(b) != r.FormValue("p") {
							http.Error(w, "web ui password wrong", 500)
							return
						}
						w.WriteHeader(200)
					})
						b, err := ioutil.ReadFile("/root/.brook.web.password")
						if err != nil {
							http.Error(w, err.Error(), 500)
							return
						}
						if string(b) != r.FormValue("p") {
							http.Error(w, "web ui password wrong", 500)
							return
						}
						b, err := ioutil.ReadFile("/root/.brook.web.password")
						if err != nil {
							http.Error(w, err.Error(), 500)
							return
						}
						if string(b) != r.FormValue("p") {
							http.Error(w, "web ui password wrong", 500)
							return
						}
						b, err := ioutil.ReadFile("/tmp/.brook.web.password")
						if err != nil {
							http.Error(w, err.Error(), 500)
							return
						}
						if string(b) != r.FormValue("p") {
							http.Error(w, "web ui password wrong", 500)
							return
						}
                            bypassGeoIP: localStorage.getItem("bypassGeoIP") ?? "",
                            disableA: localStorage.getItem("disableA") ? true : false,
                            disableAAAA: localStorage.getItem("disableAAAA") ? true : false,
                            hasp: false,
                            localhasp: false,
                            p: "",
                            var r = await fetch("/hasp");
                            if (r.status != 200) {
                                throw await r.text();
                            }
                            if((await r.text()) == "yes"){
                                this.hasp = true;
                            }
                            if(!this.hasp){
                                return;
                            }
                            if(localStorage.getItem("p")){
                                this.localhasp = true;
                            }
                            if(!this.localhasp){
                                return;
                            }
                            var r = await fetch(`/status?p=${encodeURIComponent(localStorage.getItem('p'))}`);
                        async setp() {
                            try {
                                if (!this.p.trim()) {
                                    return;
                                }
                                this.ing = true;
                                var r = await fetch(`/setp?p=${encodeURIComponent(this.p.trim())}`);
                                if (r.status != 200) {
                                    throw await r.text();
                                }
                                location.reload();
                                this.ing = false;
                            } catch (e) {
                                alert(`${e}`);
                                this.ing = false;
                            }
                        },
                        async authp() {
                            try {
                                if (!this.p.trim()) {
                                    return;
                                }
                                this.ing = true;
                                var r = await fetch(`/authp?p=${encodeURIComponent(this.p.trim())}`);
                                if (r.status != 200) {
                                    throw await r.text();
                                }
                                localStorage.setItem("p", this.p.trim());
                                location.reload();
                                this.ing = false;
                            } catch (e) {
                                alert(`${e}`);
                                this.ing = false;
                            }
                        },
                                if (
                                    this.bypassGeoIP &&
                                    this.bypassGeoIP
                                        .split(",")
                                        .map((v) => v.trim())
                                        .filter((v) => v).length
                                ) {
                                    this.bypassGeoIP
                                        .split(",")
                                        .map((v) => v.trim())
                                        .filter((v) => v)
                                        .forEach((v) => {
                                            s += ` --bypassGeoIP '${v}'`;
                                        });
                                    localStorage.setItem("bypassGeoIP", this.bypassGeoIP);
                                } else {
                                    localStorage.setItem("bypassGeoIP", "");
                                }
                                if (this.disableA) {
                                    s += ` --disableA`;
                                    localStorage.setItem("disableA", "true");
                                    localStorage.setItem("disableA", "");
                                if (this.disableAAAA) {
                                    s += ` --disableAAAA`;
                                    localStorage.setItem("disableAAAA", "true");
                                } else {
                                    localStorage.setItem("disableAAAA", "");
                                }
                                var r = await fetch(`/start?args=${encodeURIComponent(s)}&p=${encodeURIComponent(localStorage.getItem('p'))}`);
                                var r = await fetch(`/stop?p=${encodeURIComponent(localStorage.getItem('p'))}`);
        <main v-if="hasp && localhasp">
            <p>
                <label>--disableA Disable A query</label><br />
                <input type="checkbox" v-model="disableA" />
            </p>
            <p>
                <label>--disableAAAA Disable AAAA query</label><br />
                <input type="checkbox" v-model="disableAAAA" />
            </p>
                <label>--bypassGeoIP Bypass IP by Geo country code, such as CN</label><br />
                <input v-model="bypassGeoIP" placeholder="ZZ,CN" />
                <label>--blockDomainList Suffix match mode</label><br />
                <input v-model="blockDomainList" placeholder="/path/to/local/file/example_domain.txt" />
        <main v-if="!hasp">
            <p>
                <label>Set password for web UI</label><br />
                <input v-model="p" />
            </p>
            <p v-if="ing"><button disabled>Waiting...</button></p>
            <p v-if="!ing"><button v-on:click="setp">Save</button></p>
        </main>
        <main v-if="hasp && !localhasp">
            <p>
                <label>Auth web UI</label><br />
                <input v-model="p" />
            </p>
            <p v-if="ing"><button disabled>Waiting...</button></p>
            <p v-if="!ing"><button v-on:click="authp">Auth</button></p>
        </main>
func NewQUICServer(addr, password, domain string, tcpTimeout, udpTimeout int, withoutbrook bool) (*QUICServer, error) {
	if err := c.Write(4 + len(dst)); err != nil {
			l, err := c.Read()
		if err := c.Write(l); err != nil {
func (c *StreamClient) Write(l int) error {
func (c *StreamClient) Read() (int, error) {