forked from loafle/openapi-generator-original
Merge pull request #3224 from wing328/security_fix
[PHP] Better code injection handling for PHP API client
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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("*/", "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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("*/", "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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("*/", "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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("*/", "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -24,8 +24,6 @@ namespace {{modelPackage}};
|
||||
|
||||
use \ArrayAccess;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* {{classname}} Class Doc Comment
|
||||
*
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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'
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user