BinCat V4 - 實現PHP文件解析
BinCat V4 - 實現PHP文件解析
Quercus-QuercusServlet
Quercus是一個Resin實現的解析并運行php文件的jar庫,其本質是使用QuercusServlet處理所有訪問.php的文件請求,Quercus會將php文件翻譯成java class文件并在JVM中執行。
添加Quercus依賴:
<dependency>
<groupId>com.caucho</groupId>
<artifactId>quercus</artifactId>
<version>4.0.63</version>
</dependency>
然后創建一個Quercus的Servlet映射,因為BinCat只支持注解,所以無法在QuercusServlet類上添加@WebServlet注解,但是我們可以寫一個類去繼承QuercusServlet從而間接的完成Servlet聲明。
QuercusPHPServlet示例:
package com.anbai.sec.server.test.servlet;
import com.caucho.quercus.servlet.QuercusServlet;
import javax.servlet.annotation.WebServlet;
@WebServlet(name = "QuercusPHPServlet", urlPatterns = ".*\\.php$")
public class QuercusPHPServlet extends QuercusServlet {
}
BinCatConfig示例代碼(方便統一的Servlet注冊):
/**
* 手動注冊Servlet并創建BinCatServletContext對象
*
* @param appClassLoader 應用的類加載器
* @return ServletContext Servlet上下文對象
*/
public static BinCatServletContext createServletContext(BinCatWebAppClassLoader appClassLoader) throws Exception {
BinCatServletContext servletContext = new BinCatServletContext(appClassLoader);
// 手動注冊Servlet類
Class<Servlet>[] servletClass = new Class[]{
TestServlet.class,
CMDServlet.class,
QuercusPHPServlet.class
};
for (Class<Servlet> clazz : servletClass) {
Servlet servlet = clazz.newInstance();
WebServlet webServlet = clazz.getAnnotation(WebServlet.class);
if (webServlet != null) {
// 獲取WebInitParam配置
WebInitParam[] webInitParam = webServlet.initParams();
// 動態創建Servlet對象
ServletRegistration.Dynamic dynamic = servletContext.addServlet(webServlet.name(), servlet);
// 動態設置Servlet映射地址
dynamic.addMapping(webServlet.urlPatterns());
// 設置Servlet啟動參數
for (WebInitParam initParam : webInitParam) {
dynamic.setInitParameter(initParam.name(), initParam.value());
}
}
}
// 創建ServletContext
return servletContext;
}
因為QuercusServlet創建時需要必須有ServletContext對象,所以我們必須實現ServletContext接口。除此之外,Servlet創建時還需要調用Servlet的初始化方法(public void init(ServletConfig config) throws ServletException)。調用init的時候還需要實現ServletConfig接口。
初始化Servlet代碼片段:
/**
* 初始化Servlet
*
* @param servletContext Servlet上下文
* @throws ServletException Servlet處理異常
*/
public static void initServlet(BinCatServletContext servletContext) throws ServletException {
Set<BinCatServletRegistrationDynamic> dynamics = servletContext.getRegistrationDynamics();
for (BinCatServletRegistrationDynamic dynamic : dynamics) {
Servlet servlet = dynamic.getServlet();
String servletName = dynamic.getServletName();
Map<String, String> initParameterMap = dynamic.getInitParameters();
servlet.init(new BinCatServletConfig(servletContext, servletName, initParameterMap));
}
}
BinCatServletContext實現
在Servlet容器啟動的時候必須創建一個ServletContext(Servlet上下文),用于管理容器中的所有Servlet對象。在創建BinCatServletContext的時候需要創建并初始化所有的Servlet并存儲到servletMap中。
BinCatServletContext代碼片段:
package com.anbai.sec.server.servlet;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.descriptor.JspConfigDescriptor;
import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class BinCatServletContext implements ServletContext {
// 創建一個裝動態注冊的Servlet的Map
private final Map<String, Servlet> servletMap = new HashMap<>();
// 創建一個裝ServletContext初始化參數的Map
private final Map<String, String> initParameterMap = new HashMap<>();
// 創建一個裝ServletContext屬性對象的Map
private final Map<String, Object> attributeMap = new HashMap<>();
// 創建一個裝Servlet動態注冊的Set
private final Set<BinCatServletRegistrationDynamic> registrationDynamics = new LinkedHashSet<>();
// BinCatWebAppClassLoader,Web應用的類加載器
private final BinCatWebAppClassLoader appClassLoader;
public BinCatServletContext(BinCatWebAppClassLoader appClassLoader) throws Exception {
this.appClassLoader = appClassLoader;
}
// 此處省略ServletContext接口中的大部分方法,僅保留幾個示例方法...
@Override
public Servlet getServlet(String name) throws ServletException {
return servletMap.get(name);
}
@Override
public Enumeration<Servlet> getServlets() {
Set<Servlet> servlets = new HashSet<Servlet>();
servlets.addAll(servletMap.values());
return Collections.enumeration(servlets);
}
@Override
public Enumeration<String> getServletNames() {
Set<String> servlets = new HashSet<String>();
servlets.addAll(servletMap.keySet());
return Collections.enumeration(servlets);
}
}
BinCatServletConfig實現
在創建BinCatServletContext時我們指定了一個ServletConfig實現:BinCatServletConfig,ServletConfig用于指定Servlet啟動時的配置信息。
BinCatServletConfig實現:
package com.anbai.sec.server.servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
public class BinCatServletConfig implements ServletConfig {
private final BinCatServletContext servletContext;
private final WebServlet webServlet;
private final WebInitParam[] webInitParam;
public BinCatServletConfig(BinCatServletContext servletContext, WebServlet webServlet) {
this.servletContext = servletContext;
this.webServlet = webServlet;
this.webInitParam = webServlet.initParams();
}
@Override
public String getServletName() {
return webServlet.name();
}
@Override
public ServletContext getServletContext() {
return this.servletContext;
}
@Override
public String getInitParameter(String name) {
for (WebInitParam initParam : webInitParam) {
String paramName = initParam.name();
if (paramName.equals(name)) {
return initParam.value();
}
}
return null;
}
@Override
public Enumeration<String> getInitParameterNames() {
Set<String> initParamSet = new HashSet<String>();
for (WebInitParam initParam : webInitParam) {
initParamSet.add(initParam.name());
}
return Collections.enumeration(initParamSet);
}
}
BinCatDispatcherServlet實現
為了方便后續的BinCat版本處理Http請求和響應處理結果,我們簡單的封裝了BinCatDispatcherServlet和BinCatResponseHandler對象。BinCatDispatcherServlet會根據瀏覽器請求的不同URL地址去調用對應的Servlet服務,除此之外還提供了一個簡單的靜態資源文件處理邏輯和PHP解析功能。
BinCatDispatcherServlet實現代碼:
package com.anbai.sec.server.handler;
import com.anbai.sec.server.servlet.BinCatRequest;
import com.anbai.sec.server.servlet.BinCatResponse;
import com.anbai.sec.server.servlet.BinCatServletContext;
import com.anbai.sec.server.servlet.BinCatServletRegistrationDynamic;
import org.javaweb.utils.FileUtils;
import org.javaweb.utils.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Collection;
import java.util.Set;
import java.util.regex.Pattern;
public class BinCatDispatcherServlet {
public void doDispatch(BinCatRequest req, BinCatResponse resp, ByteArrayOutputStream out) throws IOException {
// 請求URI地址
String uri = req.getRequestURI();
// 獲取ServletContext
BinCatServletContext servletContext = (BinCatServletContext) req.getServletContext();
// 獲取Http請求的文件
File requestFile = new File(req.getRealPath(uri));
// 處理Http請求的靜態文件,如果文件存在(.php后綴除外)就直接返回文件內容,不需要調用Servlet
if (requestFile.exists() && requestFile.isFile() && !uri.endsWith(".php")) {
// 修改狀態碼
resp.setStatus(200, "OK");
// 解析文件的MimeType
String mimeType = Files.probeContentType(requestFile.toPath());
if (mimeType == null) {
String fileSuffix = FileUtils.getFileSuffix(requestFile.getName());
resp.setContentType("text/" + fileSuffix);
} else {
resp.setContentType(mimeType);
}
out.write(Files.readAllBytes(requestFile.toPath()));
} else {
// 遍歷所有已注冊得Servlet,處理Http請求
Set<BinCatServletRegistrationDynamic> dynamics = servletContext.getRegistrationDynamics();
for (BinCatServletRegistrationDynamic dynamic : dynamics) {
Collection<String> urlPatterns = dynamic.getMappings();
for (String urlPattern : urlPatterns) {
try {
// 檢測請求的URL地址和Servlet的地址是否匹配
if (Pattern.compile(urlPattern).matcher(uri).find()) {
// 修改狀態碼
resp.setStatus(200, "OK");
// 調用Servlet請求處理方法
dynamic.getServlet().service(req, resp);
return;
}
} catch (Exception e) {
// 修改狀態碼,輸出服務器異常信息到瀏覽器
resp.setStatus(500, "Internal Server Error");
e.printStackTrace();
out.write(("<pre>" + StringUtils.exceptionToString(e) + "</pre>").getBytes());
}
}
}
}
}
}
BinCatResponseHandler實現
BinCatResponseHandler只是一個簡單封裝的用于向瀏覽器輸出Http處理請求結果的對象。
BinCatResponseHandler實現代碼:
package com.anbai.sec.server.handler;
import com.anbai.sec.server.servlet.BinCatResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
public class BinCatResponseHandler {
public void processResult(BinCatResponse response, Map<String, String> responseHeader, String serverName,
OutputStream out, ByteArrayOutputStream baos) throws IOException {
// 處理Http響應內容
out.write(("HTTP/1.1 " + response.getStatus() + " " + response.getMessage() + "\n").getBytes());
// 輸出Web服務器信息
out.write(("Server: " + serverName + "\n").getBytes());
// 輸出返回的消息類型
out.write(("Content-Type: " + response.getContentType() + "\n").getBytes());
// 輸出返回字節數
out.write(("Content-Length: " + baos.size() + "\n").getBytes());
// 輸出用戶自定義的Header
for (String key : responseHeader.keySet()) {
out.write((key + ": " + responseHeader.get(key) + "\n").getBytes());
}
// 寫入換行
out.write("\n".getBytes());
// 將讀取到的數據寫入到客戶端Socket
out.write(baos.toByteArray());
}
}
BinCat V4實現
V4在V3的基礎上實現了ServletConfig、ServletContext接口,從而實現了Servlet的實例化和初始化,BinCatDispatcherServlet實現的Servlet服務調用。
BinCatServerV4實現代碼:
package com.anbai.sec.server;
import com.anbai.sec.server.config.BinCatConfig;
import com.anbai.sec.server.handler.BinCatDispatcherServlet;
import com.anbai.sec.server.handler.BinCatResponseHandler;
import com.anbai.sec.server.servlet.BinCatRequest;
import com.anbai.sec.server.servlet.BinCatResponse;
import com.anbai.sec.server.servlet.BinCatServletContext;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
/**
* ServerSocket Http 服務器示例
*/
public class BinCatServerV4 {
// 設置服務監聽端口
private static final int PORT = 8080;
// 設置服務名稱
private static final String SERVER_NAME = "BinCat-0.0.4";
private static final Logger LOG = Logger.getLogger("info");
public static void main(String[] args) {
try {
// 創建ServerSocket,監聽本地端口
ServerSocket ss = new ServerSocket(PORT);
// 創建BinCatServletContext對象
BinCatServletContext servletContext = BinCatConfig.createServletContext();
// 初始化Servlet
BinCatConfig.initServlet(servletContext);
LOG.info(SERVER_NAME + " 啟動成功,監聽端口: " + PORT);
while (true) {
// 等待客戶端連接
Socket socket = ss.accept();
try {
// 獲取Socket輸入流對象
InputStream in = socket.getInputStream();
// 獲取Socket輸出流對象
OutputStream out = socket.getOutputStream();
// 創建BinCat請求處理對象
BinCatRequest request = new BinCatRequest(socket, servletContext);
// 創建BinCat請求處理結果輸出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 創建BinCat請求處理結果Header對象
Map<String, String> responseHeader = new ConcurrentHashMap<String, String>();
// 創建BinCat響應處理對象
BinCatResponse response = new BinCatResponse(socket, responseHeader, baos);
// 創建BinCatDispatcherServlet對象,用于分發Http請求
BinCatDispatcherServlet dispatcherServlet = new BinCatDispatcherServlet();
// 創建BinCatResponseHandler對象,用于處理Http請求結果
BinCatResponseHandler responseHandler = new BinCatResponseHandler();
// 使用BinCatDispatcherServlet處理Servlet請求
dispatcherServlet.doDispatch(request, response, baos);
// 響應服務器處理結果
responseHandler.processResult(response, responseHeader, SERVER_NAME, out, baos);
in.close();
out.close();
} catch (Exception e) {
LOG.info("處理客戶端請求異常:" + e);
} finally {
socket.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
BinCat PHP解析測試
我們需要在javaweb-sec項目根目錄創建一個測試文件,如info.php:
<?php phpinfo();?>

啟動BinCat V4后訪問http://localhost:8080/info.php:

復制一個最新版本的Discuz到javaweb-sec目錄,嘗試安裝Discuz,訪問:http://localhost:8080/discuz/install/index.php

Discuz環境檢測正常:

測試BinCat的PHP解析功能正常,只是開始安裝Discuz時無法下一步,無異常和錯誤卡了,無法完成安裝。
Java Web安全
推薦文章: