Intro (Пропустите, если вам скучно;)
Привет, ребята, я работаю над школьным проектом, который я "унаследовал" от группы предыдущего года.Это веб-приложение, встроенное в Java EE, предназначенное для геймификации обучения пентестингу.Моя задача состояла в том, чтобы включить в приложение новую уязвимость (XXE). Существующее приложение смешивает аннотации CDI и EJB, хотя я пока не понимаю причину этого.
Ни я, ни кто-либо из моей группы не имеютлюбой опыт работы с Java EE, и хотя я приложил все усилия, чтобы набрать скорость в последние несколько недель, я немного растерялся из-за следующей проблемы, конкретно решая ее без «грязного взлома», который сломал бы шаблон MVC.
Проблема
Я могу успешно десериализовать загруженный пользователем файл .xml в экземпляр «Продукта» (или разрешить запуск эксплойта, когда пользователь загружает вредоносные данные,как я хочу).
Но сохранение объекта не работает, что, похоже, связано с областью действия моего класса UploadController.Пока что сохранение выполняется только в InitBean, который является одноэлементным и создается при запуске приложения.
Первоначальный замысел автора не позволял добавлять дополнительные объекты в базу данных после запускаapplication.
До сих пор я пробовал два маршрута: - Подражая использованию PersistenceContext из InitBean в моем UploadController (см. код ниже).Это приводит к исключению javax.persistence.TransactionRequiredException.Я прочитал об ошибке, думаю, у моего контроллера неправильный жизненный цикл, но я не совсем понял.
Я также попытался обернуть сохраняющуюся функциональность в InitBean и выставить ее как статический метод (чувствовал себя супернекрасиво, тоже не сработало).
Вопрос
Итак, в конечном итоге, мой вопрос: каков правильный подход для добавления элемента в базу данных после подачи заявкиначалось?Должен ли я сделать это из моего класса контроллера, если так, то как мне получить правильную область действия / транзакцию?Или я должен идти по другому пути полностью?Объяснения приветствуются.
Модель:
@Entity
@XmlRootElement
@Table(name = "product")
public class Product implements Serializable, Comparable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String name;
@Column(nullable = false)
private float price;
@Column(length = 512) // hibernate default of 255 not sufficient for our flags
private String description;
@Column(name = "img_path")
private String imagePath;
@ManyToOne
@JoinColumn(name = "category_fk")
private Category category;
@OneToMany(mappedBy = "product", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Recension> recensions;
@Column
private boolean active;
public Product() {
}
public Product(String name, float price, String description, String imgPath, Category category) {
this.name = name;
this.price = price;
this.description = description;
this.imagePath = imgPath;
this.category = category;
this.active = true;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getImagePath() {
return imagePath;
}
public void setImagePath(String imagePath) {
this.imagePath = imagePath;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public Set<Recension> getRecensions() {
return recensions;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
@Override
public int compareTo(Object o) {
return Long.compare(((Product) o).getId(), this.id);
}
// Convenience methods
public int ratingCount() {
return recensions.size();
}
public float averageRating() {
// avoid division by 0
if (ratingCount() == 0) {
return 0;
}
int sum = 0;
for (Recension r : recensions) {
sum += r.getRating();
}
return sum/ratingCount();
}
}
ModelDAO:
package at.technikum.mic16.prj.dao;
import at.technikum.mic16.prj.entity.Category;
import at.technikum.mic16.prj.entity.Product;
import java.io.Serializable;
import java.util.List;
import javax.enterprise.inject.Model;
import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.apache.commons.lang3.StringEscapeUtils;
/**
*
* @author leandros
*/
@Model
public class ProductDAO implements Serializable {
@PersistenceContext(unitName = "daisy-persunit")
private EntityManager em;
public Product findByID(Long id) {
return em.find(Product.class, id);
}
/**
* Find all products, optionally paginated
* @param offset Offset to result set
* @param count Number of rows to return, a value <= 0 will result in pagination being disabled
* @return
*/
public List<Product> findAll(int offset, int count) {
Query q = em.createQuery("FROM Product p WHERE active is true", Product.class);
if (count > 0) {
q.setFirstResult(offset);
q.setMaxResults(count);
}
return q.getResultList();
}
/**
* Find all products matching substring in name or description
* @param substring Substring to match
* @param offset Offset to result set
* @param count Number of rows to return, a value <= 0 will result in pagination being disabled
* @return
*/
public List<Product> findByNameOrDescription(String substring, int offset, int count) {
Query q = em.createQuery("FROM Product p WHERE (name like :substring or description like :substring) AND active is true", Product.class);
q.setParameter("substring", "%" + substring + "%");
if (count > 0) {
q.setFirstResult(offset);
q.setMaxResults(count);
}
return q.getResultList();
}
/**
* Find all products - this is vulnerable to SQL injection and also unescapes input by purpose
* Result is also sorted independently of SQL (no order by) in order to display record with reward first (highest id, see comparator)
* @param queryString
* @return
*/
public List<Product> findByExactName(String queryString) {
String unescaped = StringEscapeUtils.unescapeHtml4(queryString);
Query q = em.createQuery("FROM Product p WHERE active is true AND name = '" + unescaped + "'", Product.class);
List result = q.getResultList();
result.sort(null);
return result;
}
/**
* Find all products matching specific category
* @param category Category to match
* @param offset Offset to result set
* @param count Number of rows to return, a value <= 0 will result in pagination being disabled
* @return
*/
public List<Product> findByCategory(Category category, int offset, int count) {
Query q = em.createQuery("FROM Product p WHERE category_fk = :category AND active is true", Product.class);
q.setParameter("category", category);
if (count > 0) {
q.setFirstResult(offset);
q.setMaxResults(count);
}
return q.getResultList();
}
/**
* Find inactive products
* @return List of inactive products
*/
public List<Product> findInactive() {
Query q = em.createQuery("FROM Product p WHERE active is false", Product.class);
return q.getResultList();
}
public void persist(Product...products) {
for (Product product : products) {
em.persist(product);
}
}
public void merge(Product product) {
em.merge(product);
}
public void delete(Product product) throws EntityNotFoundException {
// attach and delete it...
Product attached = em.find(Product.class, product.getId());
if (attached != null) {
em.remove(attached);
} else {
throw new EntityNotFoundException("Product not found with id: " + product.getId());
}
}
}
InitBean
@Singleton
@LocalBean
@Startup
public class InitBean {
// file path of JS to by executed by phantom JS
private static final String XSS_FILE_PATH = "/home/daisy/.config";
// this file is intended for holding a token receivable by exploiting the command execution in admin interface
public static final String HIDDEN_FILE_PATH_CE = "/tmp/TOKEN_REWARD.TXT";
// this file is intended for holding a token receivable by exploiting the xxe vulnerability in the upload section
public static final String HIDDEN_FILE_PATH_XXE = "/tmp/hidden/TOKEN_REWARD2.TXT";
// this user bears a special description -> reward token
private static final String USER_WITH_TOKEN = "user2@foo.at";
//Admin User Testing
private static final String ADMIN_USER = "admin@foo.at";
@Inject
private WebshopService webshopService;
@Inject
private CategoryDAO categoryDAO;
@Inject
private ProductDAO productDAO;
@Inject
private OrderItemDAO orderItemDAO;
@Inject
private PlacedOrderDAO placedOrderDAO;
@Inject
private RecensionDAO recensionDAO;
@Inject
private UserDAO userDAO;
@Inject
private UserRoleDAO userRoleDAO;
private String installationToken;
private Map<Vulnerability, String> rewardTokens;
public void setInstallationToken(String installationToken) {
this.installationToken = installationToken;
}
public Map<Vulnerability, String> getRewardTokens() {
return rewardTokens;
}
public void setRewardTokens(Map<Vulnerability, String> rewardTokens) {
this.rewardTokens = rewardTokens;
}
@PostConstruct
public void init() {
try {
insertSampleData();
installationToken = webshopService.retrieveInstallationToken();
/* if there is no token, retrieving it would fail with FileNotFoundException
so just go on inserting vulnerability data...
*/
generateRewardTokens();
insertVulnerabilityData();
} catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
Logger.getLogger(InitBean.class.getName()).log(Level.SEVERE, null, ex);
} catch (FileNotFoundException ignore) {
// retrieving installation token failed
} catch (IOException ex) {
Logger.getLogger(InitBean.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* Inserts sample data into the database
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
*/
private void insertSampleData() throws NoSuchAlgorithmException, UnsupportedEncodingException {
UserRole user1Role = new UserRole("han", UserRole.Role.CUSTOMER);
UserRole user2Role = new UserRole(USER_WITH_TOKEN, UserRole.Role.CUSTOMER);
UserRole user3Role = new UserRole(ADMIN_USER, UserRole.Role.ADMIN);
userRoleDAO.persist(user1Role, user2Role, user3Role);
User user1 = new User("user1@foo.at", JBossPasswordUtil.getPasswordHash("user1"), "User", "1");
User user2 = new User(USER_WITH_TOKEN, JBossPasswordUtil.getPasswordHash(RandomStringUtils.randomAlphanumeric(12)), "User", "2");
User user3 = new User(ADMIN_USER, JBossPasswordUtil.getPasswordHash("admin"), "Iam", "God");
userDAO.persist(user1, user2, user3);
Category clothes = new Category("Clothes");
categoryDAO.persist(clothes);
Category men = new Category("Men");
men.setParent(clothes);
categoryDAO.persist(men);
Category trousersMen = new Category("Trousers");
trousersMen.setParent(men);
categoryDAO.persist(trousersMen);
Category electro = new Category("Electro");
categoryDAO.persist(electro);
Category telly = new Category("Television");
telly.setParent(electro);
categoryDAO.persist(telly);
Category hoover = new Category("Hoover");
hoover.setParent(electro);
categoryDAO.persist(hoover);
Category smartphone = new Category("Smartphone");
smartphone.setParent(electro);
categoryDAO.persist(smartphone);
Product phillips1 = new Product("Philips 55PUK4900", 679.99f, "This new Phillips is superb...", "images/products/Phillips_55PUK4900.jpg", telly);
Product phillips2 = new Product("Phillips 55PUS6031", 998.99f, "The brand new Phillips...", "images/products/Phillips_55PUS6031.jpg", telly);
Product phillips3 = new Product("Phillips 50PFK4109", 328.99f, "This new Phillips is not as good...", "images/products/Phillips_50PFK4109.jpg", telly);
Product samsung1 = new Product("Samsung UE55JU6470", 850.00f, "This new Samsung is superb...", "images/products/Samsung_UE55JU6470.jpg", telly);
Product samsung2 = new Product("Samsung UE55K5660", 1100.00f, "This new Samsung is not as good...", "images/products/Samsung_UE55K5650.jpg", telly);
Product samsung3 = new Product("Samsung UE65JU6070", 1200.99f, "This new Samsung is not as good...", "images/products/Samsung_UE65JU6070.jpg", telly);
Product panasonic1 = new Product("Panasonic TX-49DXW654", 679.99f, "This new Phillips is superb...", "images/products/Panasonic_TX49DXW654.jpg", telly);
Product panasonic2 = new Product("Panasonic TX65AXW904", 998.99f, "This new Phillips is not as good...", "images/products/Panasonic_TX65AXW904.jpg", telly);
Product panasonic3 = new Product("Panasonic TX55CXW684", 328.99f, "This new Phillips is not as good...", "images/products/Panasonic_TX55CXW684.jpg", telly);
Product hoover1 = new Product("iRobot Roomba 980", 750.90f, "Brand new and strong...", "images/products/Irobot_Roomba980.jpg", hoover);
Product hoover2 = new Product("iRobot Roomba 886", 930.90f, "Brand new and strong...", "images/products/Irobot_Roomba886.jpg", hoover);
Product hoover3 = new Product("iRobot Roomba 875", 487.90f, "Brand new and strong...", "images/products/IrobotRoomba875.jpg", hoover);
Product hoover4 = new Product("Dyson Big Ball Parquet", 640.90f, "Brand new and strong...", "images/products/Dyson_Bigball1.jpg", hoover);
Product hoover5 = new Product("Dyson DC37c Parquet", 321.90f, "Brand new and strong...", "images/products/Dyson_Dc37.jpg", hoover);
Product hoover6 = new Product("Dyson DC37 Musclehead", 219.90f, "Brand new and strong...", "images/products/Dyson_Dc37misclehead.jpg", hoover);
Product smartphone1 = new Product("Apple Iphone 7", 860.90f, "Brand new and strong...", "images/products/Iphone7.jpg", smartphone);
Product smartphone2 = new Product("Apple Iphone SE", 450.90f, "Brand new and strong...", "images/products/Iphone_SE.jpg", smartphone);
Product smartphone3 = new Product("Samsung Galaxy S8", 750.90f, "Brand new and strong...", "images/products/Samsung_S8.jpg", smartphone);
Product smartphone4 = new Product("Samsung Galaxy S6", 650.90f, "Brand new and strong...", "images/products/Samsung_S6.jpg", smartphone);
Product smartphone5 = new Product("Google Pixel", 800.90f, "Brand new and strong...", "images/products/Google_Pixel.jpg", smartphone);
Product smartphone6 = new Product("Huawei P10", 700.90f, "Brand new and strong...", "images/products/Huawei_P10.jpg", smartphone);
Product jeans1 = new Product("Lewis", 110.90f, "Brand new and strong...", "images/products/jeans1.jpg", trousersMen);
Product jeans2 = new Product("G-Star P10", 120.90f, "Brand new and strong...", "images/products/jeans2.jpg", trousersMen);
Product jeans3 = new Product("Review P10", 60.90f, "Brand new and strong...", "images/products/jeans3.jpg", trousersMen);
Product jeans4 = new Product("Replay", 75.90f, "Brand new and strong...", "images/products/jeans4.jpg", trousersMen);
Product jeans5 = new Product("Diesel", 160.90f, "Brand new and strong...", "images/products/jeans5.jpg", trousersMen);
Product jeans6 = new Product("Mustang", 55.90f, "Brand new and strong...", "images/products/jeans6.jpg", trousersMen);
productDAO.persist(phillips1, phillips2, phillips3, samsung1, samsung2, samsung3, panasonic1, panasonic2, panasonic3, hoover1, hoover2, hoover3, hoover4, hoover5, hoover6,
smartphone1, smartphone2, smartphone3, smartphone4, smartphone5, smartphone6, jeans1, jeans2, jeans3, jeans4, jeans5, jeans6);
Recension recension1 = new Recension();
recension1.setCreationDate(LocalDateTime.now().minusDays(14));
recension1.setProduct(phillips1);
recension1.setRating(4);
recension1.setUser(user1);
recension1.setText("I like it");
Recension recension2 = new Recension();
recension2.setCreationDate(LocalDateTime.now().minusDays(3).minusSeconds((int) (Math.random()*1337)));
recension2.setProduct(phillips1);
recension2.setRating(3);
recension2.setUser(user2);
recension2.setText("It's ok, don't expect too much.");
recensionDAO.persist(recension1, recension2);
}
/**
* Generates all the reward tokens in rewardTokens
*/
private void generateRewardTokens() {
rewardTokens = new HashMap<>();
for (Vulnerability v : Vulnerability.values()) {
try {
rewardTokens.put(v, DaisyPointsCrypter.encryptMessage(installationToken, "Vulnerability|" + v.name()));
//Logger.getLogger(InitBean.class.getName()).log(Level.INFO, "Generated token: ".concat(rewardTokens.get(v)));
} catch (DaisyPointsEncryptionException ex) {
rewardTokens = null;
Logger.getLogger(InitBean.class.getName()).log(Level.SEVERE, "Error generating reward tokens", ex);
}
}
}
/**
* Puts reward tokens to their respective places
* @throws IOException
*/
public void insertVulnerabilityData() throws IOException {
/*
this should only happen upon invocation via TokenController and not
on subsequent restarts, when token is already known
*/
if (rewardTokens == null) {
generateRewardTokens();
}
// hidden product - find via SQL injection
Category hoover = categoryDAO.findByName("Hoover");
Product prod1 = new Product("SQL Injection exploited!", 666, "Congratulations, here is your token for the points system:\n".concat(rewardTokens.get(Vulnerability.SQLI_PRODUCTS)), "images/thumbs_up.png", hoover);
prod1.setActive(false);
productDAO.persist(prod1);
// hidden user - find via indirect object reference
User user2 = userDAO.findById(USER_WITH_TOKEN);
user2.setDescription(rewardTokens.get(Vulnerability.INSECURE_DIRECT_OBJECT_REFERENCE));
// hidden file - find via hidden directory and CommandService
File f = new File(HIDDEN_FILE_PATH_CE);
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(f));
bw.write("Command execution exploited, here is your token for the points system:");
bw.newLine();
bw.write(rewardTokens.get(Vulnerability.HIDDEN_FILE));
bw.newLine();
bw.flush();
} finally {
FileUtil.safeClose(bw);
}
// hidden file - find via hidden directory and CommandService
writeFile(HIDDEN_FILE_PATH_CE, Vulnerability.HIDDEN_FILE);
// hidden file - find via hidden directory and CommandService
writeFile(HIDDEN_FILE_PATH_XXE, Vulnerability.XXE_UPLOAD);
// prepare phantom JS script
f = new File(XSS_FILE_PATH);
bw = null;
try {
bw = new BufferedWriter(new FileWriter(f));
bw.write(preparePhantomJSScript());
bw.flush();
} finally {
FileUtil.safeClose(bw);
}
}
private void writeFile(String pathname, Vulnerability vulnerability) throws IOException {
File f;
BufferedWriter bw;
f = new File(pathname);
bw = null;
try {
bw = new BufferedWriter(new FileWriter(f));
bw.write("Command execution exploited, here is your token for the points system:");
bw.newLine();
bw.write(rewardTokens.get(vulnerability));
bw.newLine();
bw.flush();
} finally {
FileUtil.safeClose(bw);
}
}
/**
* Delete any structures (DB reows, files) containing reward tokens
*/
public void deleteVulnerabilityData() {
for (Product p : productDAO.findInactive()) {
productDAO.delete(p);
}
User user2 = userDAO.findById(USER_WITH_TOKEN);
user2.setDescription("");
File f = new File(HIDDEN_FILE_PATH_CE);
f.delete();
f = new File(XSS_FILE_PATH);
f.delete();
}
/**
Writes token in script invoked by phantom JS
* @param token
* @return
* @throws UnsupportedEncodingException
* @throws DaisyPointsEncryptionException
*/
private String preparePhantomJSScript() throws UnsupportedEncodingException {
// this holds the phantom JS script to be executed in portable fashion
String base64 = "dmFyIHBhZ2UgPSByZXF1aXJlKCd3ZWJwYWdlJykuY3JlYXRlKCk7CgpwYWdlLnNldHRpbmdzLnVzZXJBZ2VudCA9ICdUT0tFTic7CnBhZ2Uudmlld3BvcnRTaXplID0geyB3aWR0aDogMTkyMCwgaGVpZ2h0OiAxMDgwIH07CgpwYWdlLm9wZW4oJ2h0dHA6Ly8xMjcuMC4wLjE6ODA4MC9kYWlzeS13ZWIvJywgZnVuY3Rpb24oKSB7CgogICAgICAgIHBhZ2UuZXZhbHVhdGUoZnVuY3Rpb24oKSB7CiAgICAgICAgICAgICAgICBQcmltZUZhY2VzLmFiKHtzOmRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJ1thbHQ9InByb2R1Y3QtMSJdJykuZ2V0QXR0cmlidXRlKCJpZCIpfSk7CiAgICAgICAgfSk7CgogICAgICAgIHNldFRpbWVvdXQoZnVuY3Rpb24oKSB7CiAgICAgICAgICAgICAgICBwYWdlLmV2YWx1YXRlKGZ1bmN0aW9uKCkge30pOwogICAgICAgIH0sIDIwMDApOwoKICAgICAgICBjb25zb2xlLmxvZygiZmluaXNoIik7CgogICAgICAgIHNldFRpbWVvdXQoZnVuY3Rpb24oKSB7CiAgICAgICAgICAgICAgICAvL3BhZ2UucmVuZGVyKCd0ZXN0LnBuZycpOwogICAgICAgICAgICAgICAgcGhhbnRvbS5leGl0KCk7CiAgICAgICAgfSwgMjAwMCk7Cn0pOwo=";
String script = new String(Base64.decodeBase64(base64), "UTF-8");
// modify user agent to use appropriate reward token string
return script.replace("TOKEN", rewardTokens.get(Vulnerability.XSS_REMOTE_SCRIPT));
}
//Awful Hack:
// public void persistProduct(Product product){
// productDAO.persist(product);
// }
}
UploadController
package at.technikum.mic16.prj.controller;
/**
* Created by Dave on 11/05/2018.
*/
@ManagedBean(name = "uploadController")
@ApplicationScoped
@Startup
@Singleton
//@Stateful
public class UploadController implements Serializable {
private Part file;
private String xmlString;
@Inject
private ProductDAO productDAO;
public void doOutput(Product product) {
if (product == null) return;
JAXBContext jc;
{
try {
PrintStream ps = new PrintStream(new StringOutputStream(), true);
// PrintStream ps = new PrintStream(new File("product.xml"));
System.setOut(ps);
jc = JAXBContext.newInstance(Product.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(product, System.out);
} catch (JAXBException e) {
e.printStackTrace();
}
}
}
public void upload() {
try {
setXmlString(new Scanner(file.getInputStream())
.useDelimiter("\\A").next());
Product product = deserializeProduct(getXmlString());
productDAO.persist(product);
doOutput(product);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public Part getFile() {
return file;
}
public void setFile(Part file) {
this.file = file;
}
public String getXmlString() {
return xmlString;
}
public void setXmlString(String pXmlString) {
xmlString = pXmlString;
}
//Parsing xml back to object (+vuln) -> should eventally go into another class
public Product deserializeProduct(String xmlString) {
try {
JAXBContext jc = JAXBContext.newInstance(Product.class);
XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, true);
XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(new StringReader(xmlString));
Unmarshaller unmarshaller = jc.createUnmarshaller();
return (Product) unmarshaller.unmarshal(xmlStreamReader);
} catch (JAXBException pE) {
pE.printStackTrace();
return null;
} catch (XMLStreamException pE) {
pE.printStackTrace();
return null;
}
}
}
Заранее спасибо за любую помощь!