Merge pull request #3224 from wing328/security_fix

[PHP] Better code injection handling for PHP API client
This commit is contained in:
wing328
2016-06-28 15:19:41 +08:00
committed by GitHub
95 changed files with 6473 additions and 444 deletions

View File

@@ -57,8 +57,12 @@ public interface CodegenConfig {
String escapeText(String text);
String escapeUnsafeCharacters(String input);
String escapeReservedWord(String name);
String escapeQuotationMark(String input);
String getTypeDeclaration(Property p);
String getTypeDeclaration(String name);

View File

@@ -330,16 +330,43 @@ public class DefaultCodegen {
// override with any special text escaping logic
@SuppressWarnings("static-method")
public String escapeText(String input) {
if (input != null) {
// remove \t, \n, \r
// repalce \ with \\
// repalce " with \"
// outter unescape to retain the original multi-byte characters
return StringEscapeUtils.unescapeJava(StringEscapeUtils.escapeJava(input).replace("\\/", "/")).replaceAll("[\\t\\n\\r]"," ").replace("\\", "\\\\").replace("\"", "\\\"");
if (input == null) {
return input;
}
// remove \t, \n, \r
// replace \ with \\
// replace " with \"
// outter unescape to retain the original multi-byte characters
// finally escalate characters avoiding code injection
return escapeUnsafeCharacters(StringEscapeUtils.unescapeJava(StringEscapeUtils.escapeJava(input).replace("\\/", "/")).replaceAll("[\\t\\n\\r]"," ").replace("\\", "\\\\").replace("\"", "\\\""));
}
/**
* override with any special text escaping logic to handle unsafe
* characters so as to avoid code injection
* @param input String to be cleaned up
* @return string with unsafe characters removed or escaped
*/
public String escapeUnsafeCharacters(String input) {
LOGGER.warn("escapeUnsafeCharacters should be overriden in the code generator with proper logic to escape unsafe characters");
// doing nothing by default and code generator should implement
// the logic to prevent code injection
// later we'll make this method abstract to make sure
// code generator implements this method
return input;
}
/**
* Escape single and/or double quote to avoid code injection
* @param input String to be cleaned up
* @return string with quotation mark removed or escaped
*/
public String escapeQuotationMark(String input) {
LOGGER.warn("escapeQuotationMark should be overriden in the code generator with proper logic to escape single/double quote");
return input.replace("\"", "\\\"");
}
public Set<String> defaultIncludes() {
return defaultIncludes;
}
@@ -1747,7 +1774,8 @@ public class DefaultCodegen {
int count = 0;
for (String key : consumes) {
Map<String, String> mediaType = new HashMap<String, String>();
mediaType.put("mediaType", key);
// escape quotation to avoid code injection
mediaType.put("mediaType", escapeQuotationMark(key));
count += 1;
if (count < consumes.size()) {
mediaType.put("hasMore", "true");
@@ -1780,7 +1808,8 @@ public class DefaultCodegen {
int count = 0;
for (String key : produces) {
Map<String, String> mediaType = new HashMap<String, String>();
mediaType.put("mediaType", key);
// escape quotation to avoid code injection
mediaType.put("mediaType", escapeQuotationMark(key));
count += 1;
if (count < produces.size()) {
mediaType.put("hasMore", "true");

View File

@@ -144,10 +144,10 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
if (swagger.getInfo() != null) {
Info info = swagger.getInfo();
if (info.getTitle() != null) {
config.additionalProperties().put("appName", info.getTitle());
config.additionalProperties().put("appName", config.escapeText(info.getTitle()));
}
if (info.getVersion() != null) {
config.additionalProperties().put("appVersion", info.getVersion());
config.additionalProperties().put("appVersion", config.escapeText(info.getVersion()));
}
if (info.getDescription() != null) {
config.additionalProperties().put("appDescription",
@@ -155,25 +155,25 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
}
if (info.getContact() != null) {
Contact contact = info.getContact();
config.additionalProperties().put("infoUrl", contact.getUrl());
config.additionalProperties().put("infoUrl", config.escapeText(contact.getUrl()));
if (contact.getEmail() != null) {
config.additionalProperties().put("infoEmail", contact.getEmail());
config.additionalProperties().put("infoEmail", config.escapeText(contact.getEmail()));
}
}
if (info.getLicense() != null) {
License license = info.getLicense();
if (license.getName() != null) {
config.additionalProperties().put("licenseInfo", license.getName());
config.additionalProperties().put("licenseInfo", config.escapeText(license.getName()));
}
if (license.getUrl() != null) {
config.additionalProperties().put("licenseUrl", license.getUrl());
config.additionalProperties().put("licenseUrl", config.escapeText(license.getUrl()));
}
}
if (info.getVersion() != null) {
config.additionalProperties().put("version", info.getVersion());
config.additionalProperties().put("version", config.escapeText(info.getVersion()));
}
if (info.getTermsOfService() != null) {
config.additionalProperties().put("termsOfService", info.getTermsOfService());
config.additionalProperties().put("termsOfService", config.escapeText(info.getTermsOfService()));
}
}
@@ -184,7 +184,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
StringBuilder hostBuilder = new StringBuilder();
String scheme;
if (swagger.getSchemes() != null && swagger.getSchemes().size() > 0) {
scheme = swagger.getSchemes().get(0).toValue();
scheme = config.escapeText(swagger.getSchemes().get(0).toValue());
} else {
scheme = "https";
}

View File

@@ -215,4 +215,16 @@ public class LumenServerCodegen extends DefaultCodegen implements CodegenConfig
type = swaggerType;
return toModelName(type);
}
@Override
public String escapeQuotationMark(String input) {
// remove ' to avoid code injection
return input.replace("'", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "");
}
}

View File

@@ -662,4 +662,16 @@ public class PhpClientCodegen extends DefaultCodegen implements CodegenConfig {
}
return objs;
}
@Override
public String escapeQuotationMark(String input) {
// remove ' to avoid code injection
return input.replace("'", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "");
}
}

View File

@@ -200,4 +200,15 @@ public class SilexServerCodegen extends DefaultCodegen implements CodegenConfig
return toModelName(name);
}
@Override
public String escapeQuotationMark(String input) {
// remove ' to avoid code injection
return input.replace("'", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "");
}
}

View File

@@ -225,4 +225,15 @@ public class SlimFrameworkServerCodegen extends DefaultCodegen implements Codege
return toModelName(name);
}
@Override
public String escapeQuotationMark(String input) {
// remove ' to avoid code injection
return input.replace("'", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "");
}
}

View File

@@ -85,11 +85,15 @@ use \{{invokerPackage}}\ObjectSerializer;
/**
* Operation {{{operationId}}}
*
* {{{summary}}}.
*/
{{#allParams}} // * @param {{dataType}} ${{paramName}} {{description}} {{#required}}(required){{/required}}{{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
{{/allParams}}
/**
* {{{summary}}}
*
{{#description}}
* {{.}}
*
{{/description}}
{{#allParams}}
* @param {{dataType}} ${{paramName}} {{description}} {{#required}}(required){{/required}}{{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
{{/allParams}}
* @return {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}
* @throws \{{invokerPackage}}\ApiException on non-2xx response
*/
@@ -99,21 +103,25 @@ use \{{invokerPackage}}\ObjectSerializer;
return $response;
}
/**
* Operation {{{operationId}}}WithHttpInfo
*
* {{{summary}}}.
*/
{{#allParams}} // * @param {{dataType}} ${{paramName}} {{description}} {{#required}}(required){{/required}}{{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
{{/allParams}}
/**
* {{{summary}}}
*
{{#description}}
* {{.}}
*
{{/description}}
{{#allParams}}
* @param {{dataType}} ${{paramName}} {{description}} {{#required}}(required){{/required}}{{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
{{/allParams}}
* @return Array of {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}null{{/returnType}}, HTTP status code, HTTP response headers (array of strings)
* @throws \{{invokerPackage}}\ApiException on non-2xx response
*/
public function {{operationId}}WithHttpInfo({{#allParams}}${{paramName}}{{^required}} = null{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}})
{
{{#allParams}}{{#required}}
{{#allParams}}
{{#required}}
// verify the required parameter '{{paramName}}' is set
if (${{paramName}} === null) {
throw new \InvalidArgumentException('Missing the required parameter ${{paramName}} when calling {{operationId}}');
@@ -148,7 +156,6 @@ use \{{invokerPackage}}\ObjectSerializer;
{{/hasValidation}}
{{/allParams}}
// parse inputs
$resourcePath = "{{path}}";
$httpBody = '';
@@ -161,7 +168,8 @@ use \{{invokerPackage}}\ObjectSerializer;
}
$headerParams['Content-Type'] = $this->apiClient->selectHeaderContentType(array({{#consumes}}'{{{mediaType}}}'{{#hasMore}},{{/hasMore}}{{/consumes}}));
{{#queryParams}}// query params
{{#queryParams}}
// query params
{{#collectionFormat}}
if (is_array(${{paramName}})) {
${{paramName}} = $this->apiClient->getSerializer()->serializeCollection(${{paramName}}, '{{collectionFormat}}', true);
@@ -169,8 +177,10 @@ use \{{invokerPackage}}\ObjectSerializer;
{{/collectionFormat}}
if (${{paramName}} !== null) {
$queryParams['{{baseName}}'] = $this->apiClient->getSerializer()->toQueryValue(${{paramName}});
}{{/queryParams}}
{{#headerParams}}// header params
}
{{/queryParams}}
{{#headerParams}}
// header params
{{#collectionFormat}}
if (is_array(${{paramName}})) {
${{paramName}} = $this->apiClient->getSerializer()->serializeCollection(${{paramName}}, '{{collectionFormat}}');
@@ -178,8 +188,10 @@ use \{{invokerPackage}}\ObjectSerializer;
{{/collectionFormat}}
if (${{paramName}} !== null) {
$headerParams['{{baseName}}'] = $this->apiClient->getSerializer()->toHeaderValue(${{paramName}});
}{{/headerParams}}
{{#pathParams}}// path params
}
{{/headerParams}}
{{#pathParams}}
// path params
{{#collectionFormat}}
if (is_array(${{paramName}})) {
${{paramName}} = $this->apiClient->getSerializer()->serializeCollection(${{paramName}}, '{{collectionFormat}}');
@@ -191,11 +203,13 @@ use \{{invokerPackage}}\ObjectSerializer;
$this->apiClient->getSerializer()->toPathValue(${{paramName}}),
$resourcePath
);
}{{/pathParams}}
}
{{/pathParams}}
// default format to json
$resourcePath = str_replace("{format}", "json", $resourcePath);
{{#formParams}}// form params
{{#formParams}}
// form params
if (${{paramName}} !== null) {
{{#isFile}}
// PHP 5.5 introduced a CurlFile object that deprecates the old @filename syntax
@@ -209,12 +223,14 @@ use \{{invokerPackage}}\ObjectSerializer;
{{^isFile}}
$formParams['{{baseName}}'] = $this->apiClient->getSerializer()->toFormValue(${{paramName}});
{{/isFile}}
}{{/formParams}}
}
{{/formParams}}
{{#bodyParams}}// body params
$_tempBody = null;
if (isset(${{paramName}})) {
$_tempBody = ${{paramName}};
}{{/bodyParams}}
}
{{/bodyParams}}
// for model (json/xml)
if (isset($_tempBody)) {
@@ -222,19 +238,26 @@ use \{{invokerPackage}}\ObjectSerializer;
} elseif (count($formParams) > 0) {
$httpBody = $formParams; // for HTTP post (form)
}
{{#authMethods}}{{#isApiKey}}
{{#authMethods}}
{{#isApiKey}}
// this endpoint requires API key authentication
$apiKey = $this->apiClient->getApiKeyWithPrefix('{{keyParamName}}');
if (strlen($apiKey) !== 0) {
{{#isKeyInHeader}}$headerParams['{{keyParamName}}'] = $apiKey;{{/isKeyInHeader}}{{#isKeyInQuery}}$queryParams['{{keyParamName}}'] = $apiKey;{{/isKeyInQuery}}
}{{/isApiKey}}
{{#isBasic}}// this endpoint requires HTTP basic authentication
}
{{/isApiKey}}
{{#isBasic}}
// this endpoint requires HTTP basic authentication
if (strlen($this->apiClient->getConfig()->getUsername()) !== 0 or strlen($this->apiClient->getConfig()->getPassword()) !== 0) {
$headerParams['Authorization'] = 'Basic ' . base64_encode($this->apiClient->getConfig()->getUsername() . ":" . $this->apiClient->getConfig()->getPassword());
}{{/isBasic}}{{#isOAuth}}// this endpoint requires OAuth (access token)
}
{{/isBasic}}
{{#isOAuth}}
// this endpoint requires OAuth (access token)
if (strlen($this->apiClient->getConfig()->getAccessToken()) !== 0) {
$headerParams['Authorization'] = 'Bearer ' . $this->apiClient->getConfig()->getAccessToken();
}{{/isOAuth}}
}
{{/isOAuth}}
{{/authMethods}}
// make the API Call
try {
@@ -268,6 +291,7 @@ use \{{invokerPackage}}\ObjectSerializer;
throw $e;
}
}
{{/operation}}
}
{{/operations}}

View File

@@ -24,8 +24,6 @@ namespace {{modelPackage}};
use \ArrayAccess;
/**
* {{classname}} Class Doc Comment
*

View File

@@ -2,11 +2,12 @@
{{#appName}}
* {{{appName}}}
*
{{/appName}} */
{{/appName}}
{{#appDescription}}
//* {{{appDescription}}}
* {{{appDescription}}}
*
{{/appDescription}}
/* {{#version}}OpenAPI spec version: {{{version}}}{{/version}}
* {{#version}}OpenAPI spec version: {{{version}}}{{/version}}
* {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}}
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*

View File

@@ -0,0 +1,68 @@
swagger: '2.0'
info:
description: "This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\ */ ' \" =end"
version: 1.0.0 */ ' " =end
title: Swagger Petstore */ ' " =end
termsOfService: http://swagger.io/terms/ */ ' " =end
contact:
email: apiteam@swagger.io */ ' " =end
license:
name: Apache 2.0 */ ' " =end
url: http://www.apache.org/licenses/LICENSE-2.0.html */ ' " =end
host: petstore.swagger.io */ ' " =end
basePath: /v2 */ ' " =end
tags:
- name: fake
description: Everything about your Pets */ ' " =end
externalDocs:
description: Find out more */ ' " = end
url: 'http://swagger.io'
schemes:
- http */ end ' "
paths:
/fake:
put:
tags:
- fake
summary: To test code injection */ ' " =end
descriptions: To test code injection */ ' " =end
operationId: testCodeInject */ ' " =end
consumes:
- application/json
- "*/ ' \" =end"
produces:
- application/json
- "*/ ' \" =end"
parameters:
- name: test code inject */ ' " =end
type: string
in: formData
description: To test code injection */ ' " =end
responses:
'400':
description: To test code injection */ ' " =end
securityDefinitions:
petstore_auth:
type: oauth2
authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog'
flow: implicit
scopes:
'write:pets': modify pets in your account */ ' " =end
'read:pets': read your pets */ ' " =end
api_key:
type: apiKey
name: api_key */ ' " =end
in: header
definitions:
Return:
description: Model for testing reserved words */ ' " =end
properties:
return:
description: property description */ ' " =end
type: integer
format: int32
xml:
name: Return
externalDocs:
description: Find out more about Swagger */ ' " =end
url: 'http://swagger.io'

View File

@@ -1,16 +1,16 @@
swagger: '2.0'
info:
description: 'This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: " \ '
version: 1.0.0
title: Swagger Petstore
description: "This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\"
version: 1.0.0
title: Swagger Petstore
termsOfService: 'http://swagger.io/terms/'
contact:
email: apiteam@swagger.io
email: apiteam@swagger.io
license:
name: Apache 2.0
name: Apache 2.0
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
host: petstore.swagger.io
basePath: /v2
host: petstore.swagger.io
basePath: /v2
tags:
- name: pet
description: Everything about your Pets
@@ -561,6 +561,26 @@ paths:
description: User not found
/fake:
put:
tags:
- fake
summary: To test code injection */ =end
descriptions: To test code injection */ =end
operationId: testCodeInject */ =end
consumes:
- application/json
- "*/ =end'));(phpinfo('"
produces:
- application/json
- '*/ end'
parameters:
- name: test code inject */ =end
type: string
in: formData
description: To test code injection */ =end
responses:
'400':
description: To test code injection */ =end
get:
tags:
- fake